// Prisma Schema for tOS - Team Operating System // Database: PostgreSQL generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ============================================================================= // ENUMS // ============================================================================= enum ContractType { FULL_TIME PART_TIME MINI_JOB INTERN WORKING_STUDENT FREELANCE TEMPORARY } enum AbsenceType { VACATION SICK SICK_CHILD SPECIAL_LEAVE UNPAID_LEAVE PARENTAL_LEAVE HOME_OFFICE BUSINESS_TRIP TRAINING COMPENSATION } enum ApprovalStatus { PENDING APPROVED REJECTED CANCELLED } enum TimeEntryType { REGULAR OVERTIME HOLIDAY SICK VACATION CORRECTION } enum ReviewType { PROBATION ANNUAL PROMOTION PROJECT FEEDBACK EXIT } enum S3StatusType { GREEN YELLOW RED NOT_APPLICABLE } enum S3Type { SEIRI // Sort - Aussortieren SEITON // Set in Order - Aufräumen SEISO // Shine - Sauberkeit } enum SkillLevel { NONE BEGINNER INTERMEDIATE ADVANCED EXPERT TRAINER } enum IntegrationType { DATEV PERSONIO SAP CALENDAR EMAIL CUSTOM PLENTYONE ZULIP TODOIST FREESCOUT NEXTCLOUD ECODMS GEMBADOCS } enum SyncStatus { PENDING RUNNING SUCCESS ERROR } // SQCDM Categories for Morning Meeting enum SQCDMCategory { SAFETY // S - Sicherheit QUALITY // Q - Qualitaet COST // C - Kosten DELIVERY // D - Lieferung MORALE // M - Moral/Mitarbeiter } // Meeting Status enum MeetingStatus { SCHEDULED IN_PROGRESS COMPLETED CANCELLED } // KPI Status (Traffic Light) enum KPIStatus { GREEN YELLOW RED NEUTRAL } // Trend Direction enum Trend { UP DOWN STABLE } // Action Status enum ActionStatus { OPEN IN_PROGRESS COMPLETED CANCELLED } // Priority Level enum Priority { LOW MEDIUM HIGH CRITICAL } // ============================================================================= // CORE MODELS // ============================================================================= model User { id String @id @default(cuid()) email String @unique firstName String lastName String keycloakId String? @unique avatar String? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations departmentId String? department Department? @relation("UserDepartment", fields: [departmentId], references: [id]) roles UserRole[] employee Employee? preferences UserPreference? auditLogs AuditLog[] // Manager relations managedDepartments Department[] @relation("DepartmentManager") correctedTimeEntries TimeEntry[] @relation("TimeEntryCorrectedBy") approvedAbsences Absence[] @relation("AbsenceApprovedBy") conductedReviews EmployeeReview[] @relation("ReviewConductedBy") skillAssessments SkillMatrixEntry[] @relation("SkillAssessedBy") integrationCredentials IntegrationCredential[] // LEAN 3S relations s3PlansCreated S3Plan[] @relation("S3PlanCreatedBy") s3StatusCompleted S3Status[] @relation("S3StatusCompletedBy") // Morning Meeting relations conductedMeetings MorningMeeting[] @relation("MeetingConductor") assignedMeetingActions MorningMeetingAction[] @relation("ActionAssignee") @@index([email]) @@index([keycloakId]) @@index([departmentId]) } model Department { id String @id @default(cuid()) name String code String? @unique isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Self-relation for hierarchy parentId String? parent Department? @relation("DepartmentHierarchy", fields: [parentId], references: [id]) children Department[] @relation("DepartmentHierarchy") // Manager relation managerId String? manager User? @relation("DepartmentManager", fields: [managerId], references: [id]) // Relations users User[] @relation("UserDepartment") onboardingTasks OnboardingTask[] s3Plans S3Plan[] skills Skill[] morningMeetings MorningMeeting[] @@index([parentId]) @@index([managerId]) } model Role { id String @id @default(cuid()) name String @unique description String? permissions Json @default("[]") isSystem Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations users UserRole[] @@index([name]) } model UserRole { id String @id @default(cuid()) userId String roleId String createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) @@unique([userId, roleId]) @@index([userId]) @@index([roleId]) } // ============================================================================= // HR MODELS // ============================================================================= model Employee { id String @id @default(cuid()) employeeNumber String @unique position String entryDate DateTime exitDate DateTime? contractType ContractType workingHours Float @default(40) salary String? // Encrypted salary (AES-256-GCM) taxClass Int? bankAccount Json? // Encrypted bank account (AES-256-GCM) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations userId String @unique user User @relation(fields: [userId], references: [id], onDelete: Cascade) timeEntries TimeEntry[] absences Absence[] reviews EmployeeReview[] skillMatrixEntries SkillMatrixEntry[] @@index([employeeNumber]) @@index([userId]) @@index([entryDate]) } model TimeEntry { id String @id @default(cuid()) date DateTime @db.Date clockIn DateTime? clockOut DateTime? breakMinutes Int @default(0) type TimeEntryType @default(REGULAR) note String? isLocked Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations employeeId String employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade) correctedById String? correctedBy User? @relation("TimeEntryCorrectedBy", fields: [correctedById], references: [id]) @@unique([employeeId, date]) @@index([employeeId]) @@index([date]) @@index([correctedById]) } model Absence { id String @id @default(cuid()) type AbsenceType startDate DateTime @db.Date endDate DateTime @db.Date days Float status ApprovalStatus @default(PENDING) note String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations employeeId String employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade) approvedById String? approvedBy User? @relation("AbsenceApprovedBy", fields: [approvedById], references: [id]) @@index([employeeId]) @@index([startDate, endDate]) @@index([status]) @@index([approvedById]) } model EmployeeReview { id String @id @default(cuid()) date DateTime type ReviewType notes String? @db.Text goals Json? rating Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations employeeId String employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade) reviewerId String reviewer User @relation("ReviewConductedBy", fields: [reviewerId], references: [id]) @@index([employeeId]) @@index([reviewerId]) @@index([date]) @@index([type]) } model OnboardingTask { id String @id @default(cuid()) title String description String? @db.Text dueOffset Int @default(0) // Days from entry date assignedTo String? // Role or specific user type sortOrder Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations departmentId String? department Department? @relation(fields: [departmentId], references: [id]) @@index([departmentId]) @@index([sortOrder]) } // ============================================================================= // INTEGRATION MODELS // ============================================================================= model IntegrationCredential { id String @id @default(cuid()) type IntegrationType name String credentials String // Encrypted JSON (AES-256-GCM) isActive Boolean @default(true) lastUsed DateTime? lastSync DateTime? syncStatus SyncStatus @default(PENDING) syncError String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdById String createdBy User @relation(fields: [createdById], references: [id]) // Relations syncHistory IntegrationSyncHistory[] @@unique([type, name]) @@index([type]) @@index([isActive]) @@index([createdById]) } model IntegrationSyncHistory { id String @id @default(cuid()) credentialId String credential IntegrationCredential @relation(fields: [credentialId], references: [id], onDelete: Cascade) status SyncStatus startedAt DateTime @default(now()) completedAt DateTime? duration Int? // Duration in milliseconds recordsProcessed Int? error String? metadata Json? // Additional sync metadata createdAt DateTime @default(now()) @@index([credentialId]) @@index([status]) @@index([startedAt]) } // ============================================================================= // LEAN MODELS - 3S / 5S // ============================================================================= model S3Plan { id String @id @default(cuid()) year Int month Int isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations departmentId String department Department @relation(fields: [departmentId], references: [id]) createdById String createdBy User @relation("S3PlanCreatedBy", fields: [createdById], references: [id]) categories S3Category[] @@unique([departmentId, year, month]) @@index([departmentId]) @@index([year, month]) @@index([createdById]) } model S3Category { id String @id @default(cuid()) name String s3Type S3Type // SEIRI, SEITON, SEISO sortOrder Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations planId String plan S3Plan @relation(fields: [planId], references: [id], onDelete: Cascade) statuses S3Status[] @@index([planId]) @@index([sortOrder]) @@index([s3Type]) } model S3Status { id String @id @default(cuid()) week Int // 1-53 status S3StatusType photo String? // URL to uploaded image note String? completedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations categoryId String category S3Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) completedById String? completedBy User? @relation("S3StatusCompletedBy", fields: [completedById], references: [id]) @@unique([categoryId, week]) @@index([categoryId]) @@index([week]) @@index([status]) @@index([completedById]) } // ============================================================================= // LEAN MODELS - Morning Meeting (Shopfloor Management) // ============================================================================= model MorningMeeting { id String @id @default(cuid()) date DateTime @db.Date startTime DateTime? // Actual start time endTime DateTime? // Actual end time duration Int? // Duration in minutes (calculated) participants Json? // Array of participant user IDs notes String? @db.Text status MeetingStatus @default(SCHEDULED) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations departmentId String department Department @relation(fields: [departmentId], references: [id]) conductorId String? conductor User? @relation("MeetingConductor", fields: [conductorId], references: [id]) topics MorningMeetingTopic[] actions MorningMeetingAction[] @@unique([departmentId, date]) @@index([departmentId]) @@index([date]) @@index([status]) @@index([conductorId]) } model MorningMeetingTopic { id String @id @default(cuid()) category SQCDMCategory // SAFETY, QUALITY, COST, DELIVERY, MORALE title String value String? // Current value target String? // Target value unit String? // Unit (%, pieces, EUR, etc.) status KPIStatus @default(NEUTRAL) trend Trend? // UP, DOWN, STABLE sortOrder Int @default(0) note String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations meetingId String meeting MorningMeeting @relation(fields: [meetingId], references: [id], onDelete: Cascade) @@index([meetingId]) @@index([category]) @@index([sortOrder]) @@index([status]) } model MorningMeetingAction { id String @id @default(cuid()) title String description String? @db.Text dueDate DateTime? @db.Date status ActionStatus @default(OPEN) priority Priority @default(MEDIUM) completedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations meetingId String meeting MorningMeeting @relation(fields: [meetingId], references: [id], onDelete: Cascade) assigneeId String? assignee User? @relation("ActionAssignee", fields: [assigneeId], references: [id]) @@index([meetingId]) @@index([assigneeId]) @@index([status]) @@index([priority]) @@index([dueDate]) } // ============================================================================= // LEAN MODELS - Skill Matrix // ============================================================================= model Skill { id String @id @default(cuid()) name String description String? category String? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations departmentId String? department Department? @relation(fields: [departmentId], references: [id]) entries SkillMatrixEntry[] @@unique([name, departmentId]) @@index([departmentId]) @@index([category]) } model SkillMatrixEntry { id String @id @default(cuid()) level SkillLevel @default(NONE) assessedAt DateTime @default(now()) note String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations employeeId String employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade) skillId String skill Skill @relation(fields: [skillId], references: [id], onDelete: Cascade) assessedById String? assessedBy User? @relation("SkillAssessedBy", fields: [assessedById], references: [id]) @@unique([employeeId, skillId]) @@index([employeeId]) @@index([skillId]) @@index([assessedById]) @@index([level]) } // ============================================================================= // USER PREFERENCES // ============================================================================= model UserPreference { id String @id @default(cuid()) userId String @unique user User @relation(fields: [userId], references: [id], onDelete: Cascade) theme String @default("system") // "light", "dark", "system" language String @default("de") // "de", "en" dashboardLayout Json? // Array of widget configurations notifications Json? // Notification preferences createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) } // ============================================================================= // AUDIT LOGGING // ============================================================================= model AuditLog { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id]) action String // CREATE, UPDATE, DELETE, LOGIN, LOGOUT, etc. entity String // User, Department, Employee, etc. entityId String? oldData Json? newData Json? ipAddress String? userAgent String? createdAt DateTime @default(now()) @@index([userId]) @@index([entity, entityId]) @@index([action]) @@index([createdAt]) } // ============================================================================= // SYSTEM SETTINGS // ============================================================================= model SystemSetting { id String @id @default(cuid()) key String @unique value String category String description String? valueType String @default("string") isSecret Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([category]) }