feat: complete tOS project with HR, LEAN, Dashboard and Integrations modules
Full enterprise web operating system including: - Next.js 14 frontend with App Router, i18n (DE/EN), shadcn/ui - NestJS 10 backend with Prisma, JWT auth, Swagger docs - Keycloak 24 SSO with role-based access control - HR module (employees, time tracking, absences, org chart) - LEAN module (3S planning, morning meeting SQCDM, skill matrix) - Integrations module (PlentyONE, Zulip, Todoist, FreeScout, Nextcloud, ecoDMS, GembaDocs) - Dashboard with customizable drag & drop widget grid - Docker Compose infrastructure (PostgreSQL 16, Redis 7, Keycloak 24) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
656
apps/api/prisma/schema.prisma
Normal file
656
apps/api/prisma/schema.prisma
Normal file
@@ -0,0 +1,656 @@
|
||||
// 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])
|
||||
}
|
||||
215
apps/api/prisma/seed.ts
Normal file
215
apps/api/prisma/seed.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('Seeding database...');
|
||||
|
||||
// Create default roles
|
||||
const roles = await Promise.all([
|
||||
prisma.role.upsert({
|
||||
where: { name: 'admin' },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'admin',
|
||||
description: 'Administrator with full system access',
|
||||
permissions: JSON.stringify([
|
||||
'users:read',
|
||||
'users:write',
|
||||
'users:delete',
|
||||
'employees:read',
|
||||
'employees:write',
|
||||
'employees:delete',
|
||||
'departments:read',
|
||||
'departments:write',
|
||||
'departments:delete',
|
||||
'roles:read',
|
||||
'roles:write',
|
||||
'roles:delete',
|
||||
'settings:read',
|
||||
'settings:write',
|
||||
]),
|
||||
isSystem: true,
|
||||
},
|
||||
}),
|
||||
prisma.role.upsert({
|
||||
where: { name: 'hr-manager' },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'hr-manager',
|
||||
description: 'HR Manager with access to employee data',
|
||||
permissions: JSON.stringify([
|
||||
'users:read',
|
||||
'users:write',
|
||||
'employees:read',
|
||||
'employees:write',
|
||||
'departments:read',
|
||||
'absences:read',
|
||||
'absences:write',
|
||||
'time-entries:read',
|
||||
'time-entries:write',
|
||||
'reviews:read',
|
||||
'reviews:write',
|
||||
]),
|
||||
isSystem: true,
|
||||
},
|
||||
}),
|
||||
prisma.role.upsert({
|
||||
where: { name: 'team-lead' },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'team-lead',
|
||||
description: 'Team leader with access to team data',
|
||||
permissions: JSON.stringify([
|
||||
'users:read',
|
||||
'employees:read',
|
||||
'departments:read',
|
||||
'absences:read',
|
||||
'absences:approve',
|
||||
'time-entries:read',
|
||||
'reviews:read',
|
||||
'reviews:write',
|
||||
'skills:read',
|
||||
'skills:write',
|
||||
]),
|
||||
isSystem: true,
|
||||
},
|
||||
}),
|
||||
prisma.role.upsert({
|
||||
where: { name: 'employee' },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'employee',
|
||||
description: 'Regular employee with basic access',
|
||||
permissions: JSON.stringify([
|
||||
'profile:read',
|
||||
'profile:write',
|
||||
'absences:read',
|
||||
'absences:request',
|
||||
'time-entries:read',
|
||||
'time-entries:write',
|
||||
'skills:read',
|
||||
]),
|
||||
isSystem: true,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
console.log(`Created ${roles.length} roles`);
|
||||
|
||||
// Create default departments
|
||||
const departments = await Promise.all([
|
||||
prisma.department.upsert({
|
||||
where: { code: 'MGMT' },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'Management',
|
||||
code: 'MGMT',
|
||||
},
|
||||
}),
|
||||
prisma.department.upsert({
|
||||
where: { code: 'HR' },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'Human Resources',
|
||||
code: 'HR',
|
||||
},
|
||||
}),
|
||||
prisma.department.upsert({
|
||||
where: { code: 'ENG' },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'Engineering',
|
||||
code: 'ENG',
|
||||
},
|
||||
}),
|
||||
prisma.department.upsert({
|
||||
where: { code: 'PROD' },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'Production',
|
||||
code: 'PROD',
|
||||
},
|
||||
}),
|
||||
prisma.department.upsert({
|
||||
where: { code: 'QA' },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'Quality Assurance',
|
||||
code: 'QA',
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
console.log(`Created ${departments.length} departments`);
|
||||
|
||||
// Create admin user (for development)
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const adminRole = roles.find((r) => r.name === 'admin');
|
||||
const mgmtDept = departments.find((d) => d.code === 'MGMT');
|
||||
|
||||
if (adminRole && mgmtDept) {
|
||||
const adminUser = await prisma.user.upsert({
|
||||
where: { email: 'admin@tos.local' },
|
||||
update: {},
|
||||
create: {
|
||||
email: 'admin@tos.local',
|
||||
firstName: 'System',
|
||||
lastName: 'Administrator',
|
||||
departmentId: mgmtDept.id,
|
||||
roles: {
|
||||
create: {
|
||||
roleId: adminRole.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Created admin user: ${adminUser.email}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create default skills
|
||||
const skills = await Promise.all([
|
||||
prisma.skill.upsert({
|
||||
where: { name_departmentId: { name: 'Communication', departmentId: null as unknown as string } },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'Communication',
|
||||
description: 'Effective verbal and written communication',
|
||||
category: 'Soft Skills',
|
||||
},
|
||||
}),
|
||||
prisma.skill.upsert({
|
||||
where: { name_departmentId: { name: 'Problem Solving', departmentId: null as unknown as string } },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'Problem Solving',
|
||||
description: 'Analytical thinking and problem resolution',
|
||||
category: 'Soft Skills',
|
||||
},
|
||||
}),
|
||||
prisma.skill.upsert({
|
||||
where: { name_departmentId: { name: 'Team Collaboration', departmentId: null as unknown as string } },
|
||||
update: {},
|
||||
create: {
|
||||
name: 'Team Collaboration',
|
||||
description: 'Working effectively in a team environment',
|
||||
category: 'Soft Skills',
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
console.log(`Created ${skills.length} skills`);
|
||||
|
||||
console.log('Database seeding completed!');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error('Error seeding database:', e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user