// Planarchy — Prisma Schema // All monetary values stored as integer cents to avoid float precision issues. generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ─── Enums ─────────────────────────────────────────────────────────────────── enum SystemRole { ADMIN MANAGER CONTROLLER USER VIEWER } enum OrderType { BD CHARGEABLE INTERNAL OVERHEAD } enum AllocationType { INT EXT } enum AllocationStatus { PROPOSED CONFIRMED ACTIVE COMPLETED CANCELLED } enum BlueprintTarget { RESOURCE PROJECT } enum ProjectStatus { DRAFT ACTIVE ON_HOLD COMPLETED CANCELLED } enum EstimateStatus { DRAFT IN_REVIEW APPROVED ARCHIVED } enum EstimateVersionStatus { WORKING BASELINE SUBMITTED APPROVED SUPERSEDED } enum EstimateExportFormat { XLSX CSV JSON SAP MMP } enum ResourceType { EMPLOYEE FREELANCER APPRENTICE INTERN STUDENT } enum AuditAction { CREATE UPDATE DELETE SHIFT IMPORT } enum VacationType { ANNUAL SICK PUBLIC_HOLIDAY OTHER } enum VacationStatus { PENDING APPROVED REJECTED CANCELLED } enum ImportBatchStatus { DRAFT STAGING STAGED REVIEW_READY APPROVED COMMITTING COMMITTED FAILED CANCELLED } enum StagedRecordStatus { PARSED NORMALIZED UNRESOLVED APPROVED REJECTED COMMITTED FAILED } enum DispoImportSourceKind { REFERENCE PLANNING CHARGEABILITY ROSTER } enum DispoStagedRecordType { RESOURCE CLIENT PROJECT ASSIGNMENT VACATION AVAILABILITY_RULE UNRESOLVED } // ─── Auth.js Compatible Models ─────────────────────────────────────────────── model User { id String @id @default(cuid()) name String? email String @unique emailVerified DateTime? image String? passwordHash String? systemRole SystemRole @default(USER) permissionOverrides Json? @db.JsonB dashboardLayout Json? @db.JsonB columnPreferences Json? @db.JsonB accounts Account[] sessions Session[] auditLogs AuditLog[] vacationsRequested Vacation[] @relation("vacation_requested") vacationsApproved Vacation[] @relation("vacation_approved") resource Resource? notifications Notification[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("users") } model Account { id String @id @default(cuid()) userId String type String provider String providerAccountId String refresh_token String? @db.Text access_token String? @db.Text expires_at Int? token_type String? scope String? id_token String? @db.Text session_state String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) @@map("accounts") } model Session { id String @id @default(cuid()) sessionToken String @unique userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("sessions") } model VerificationToken { identifier String token String @unique expires DateTime @@unique([identifier, token]) @@map("verification_tokens") } // ─── Import Staging ─────────────────────────────────────────────────────────── model ImportBatch { id String @id @default(cuid()) sourceSystem String @default("DISPO_V2") status ImportBatchStatus @default(DRAFT) referenceSourceFile String? planningSourceFile String? chargeabilitySourceFile String? notes String? summary Json @db.JsonB @default("{}") startedAt DateTime? stagedAt DateTime? approvedAt DateTime? committedAt DateTime? failedAt DateTime? stagedResources StagedResource[] stagedClients StagedClient[] stagedProjects StagedProject[] stagedAssignments StagedAssignment[] stagedVacations StagedVacation[] stagedAvailabilityRules StagedAvailabilityRule[] stagedUnresolvedRecords StagedUnresolvedRecord[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status]) @@map("import_batches") } model StagedResource { id String @id @default(cuid()) importBatchId String status StagedRecordStatus @default(PARSED) sourceKind DispoImportSourceKind sourceWorkbook String sourceSheet String sourceRow Int sourceColumn String? canonicalExternalId String enterpriseId String? eid String? displayName String? email String? chapter String? chapterCode String? managementLevelGroupName String? managementLevelName String? countryCode String? metroCityName String? clientUnitName String? resourceType ResourceType? chargeabilityTarget Float? fte Float? lcrCents Int? ucrCents Int? availability Json? @db.JsonB roleTokens String[] @default([]) warnings String[] @default([]) errorMessage String? rawPayload Json @db.JsonB normalizedData Json @db.JsonB @default("{}") importBatch ImportBatch @relation(fields: [importBatchId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([importBatchId, status]) @@index([canonicalExternalId]) @@map("staged_resources") } model StagedClient { id String @id @default(cuid()) importBatchId String status StagedRecordStatus @default(PARSED) sourceKind DispoImportSourceKind sourceWorkbook String sourceSheet String sourceRow Int sourceColumn String? clientCode String? parentClientCode String? name String sortOrder Int? isActive Boolean @default(true) warnings String[] @default([]) errorMessage String? rawPayload Json @db.JsonB normalizedData Json @db.JsonB @default("{}") importBatch ImportBatch @relation(fields: [importBatchId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([importBatchId, status]) @@index([clientCode]) @@map("staged_clients") } model StagedProject { id String @id @default(cuid()) importBatchId String status StagedRecordStatus @default(PARSED) sourceKind DispoImportSourceKind sourceWorkbook String sourceSheet String sourceRow Int sourceColumn String? projectKey String shortCode String? name String? clientCode String? utilizationCategoryCode String? orderType OrderType? allocationType AllocationType? winProbability Int? isInternal Boolean @default(false) isTbd Boolean @default(false) startDate DateTime? @db.Date endDate DateTime? @db.Date warnings String[] @default([]) errorMessage String? rawPayload Json @db.JsonB normalizedData Json @db.JsonB @default("{}") importBatch ImportBatch @relation(fields: [importBatchId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([importBatchId, status]) @@index([projectKey]) @@map("staged_projects") } model StagedAssignment { id String @id @default(cuid()) importBatchId String status StagedRecordStatus @default(PARSED) sourceKind DispoImportSourceKind sourceWorkbook String sourceSheet String sourceRow Int sourceColumn String? resourceExternalId String projectKey String? assignmentDate DateTime? @db.Date startDate DateTime? @db.Date endDate DateTime? @db.Date hoursPerDay Float? percentage Float? slotFraction Float? roleToken String? roleName String? chapterToken String? utilizationCategoryCode String? winProbability Int? isInternal Boolean @default(false) isUnassigned Boolean @default(false) isTbd Boolean @default(false) warnings String[] @default([]) errorMessage String? rawPayload Json @db.JsonB normalizedData Json @db.JsonB @default("{}") importBatch ImportBatch @relation(fields: [importBatchId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([importBatchId, status]) @@index([resourceExternalId]) @@index([projectKey]) @@index([assignmentDate]) @@map("staged_assignments") } model StagedVacation { id String @id @default(cuid()) importBatchId String status StagedRecordStatus @default(PARSED) sourceKind DispoImportSourceKind sourceWorkbook String sourceSheet String sourceRow Int sourceColumn String? resourceExternalId String vacationType VacationType startDate DateTime @db.Date endDate DateTime @db.Date note String? holidayName String? isHalfDay Boolean @default(false) halfDayPart String? isPublicHoliday Boolean @default(false) warnings String[] @default([]) errorMessage String? rawPayload Json @db.JsonB normalizedData Json @db.JsonB @default("{}") importBatch ImportBatch @relation(fields: [importBatchId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([importBatchId, status]) @@index([resourceExternalId]) @@index([startDate, endDate]) @@map("staged_vacations") } model StagedAvailabilityRule { id String @id @default(cuid()) importBatchId String status StagedRecordStatus @default(PARSED) sourceKind DispoImportSourceKind sourceWorkbook String sourceSheet String sourceRow Int sourceColumn String? resourceExternalId String ruleType String weekday Int? effectiveStartDate DateTime? @db.Date effectiveEndDate DateTime? @db.Date availableHours Float? percentage Float? isResolved Boolean @default(false) warnings String[] @default([]) errorMessage String? rawPayload Json @db.JsonB normalizedData Json @db.JsonB @default("{}") importBatch ImportBatch @relation(fields: [importBatchId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([importBatchId, status]) @@index([resourceExternalId]) @@map("staged_availability_rules") } model StagedUnresolvedRecord { id String @id @default(cuid()) importBatchId String status StagedRecordStatus @default(UNRESOLVED) sourceKind DispoImportSourceKind sourceWorkbook String sourceSheet String sourceRow Int sourceColumn String? recordType DispoStagedRecordType resourceExternalId String? projectKey String? message String resolutionHint String? warnings String[] @default([]) rawPayload Json @db.JsonB normalizedData Json @db.JsonB @default("{}") importBatch ImportBatch @relation(fields: [importBatchId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([importBatchId, status]) @@index([recordType]) @@index([resourceExternalId]) @@index([projectKey]) @@map("staged_unresolved_records") } // ─── Country / Metro City ──────────────────────────────────────────────────── model Country { id String @id @default(cuid()) code String @unique // ISO 3166-1 alpha-2 (DE, IN, ES, ...) name String dailyWorkingHours Float @default(8.0) scheduleRules Json? @db.JsonB // e.g. Spain variable schedule isActive Boolean @default(true) metroCities MetroCity[] resources Resource[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("countries") } model MetroCity { id String @id @default(cuid()) name String countryId String country Country @relation(fields: [countryId], references: [id]) resources Resource[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([countryId, name]) @@map("metro_cities") } // ─── Org Unit Hierarchy ───────────────────────────────────────────────────── model OrgUnit { id String @id @default(cuid()) name String shortName String? level Int // 5, 6, or 7 parentId String? parent OrgUnit? @relation("OrgUnitTree", fields: [parentId], references: [id]) children OrgUnit[] @relation("OrgUnitTree") sortOrder Int @default(0) isActive Boolean @default(true) resources Resource[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([parentId, name]) @@index([level]) @@map("org_units") } // ─── Utilization Category ─────────────────────────────────────────────────── model UtilizationCategory { id String @id @default(cuid()) code String @unique // "Chg", "BD", "MD&I", etc. name String description String? sortOrder Int @default(0) isActive Boolean @default(true) isDefault Boolean @default(false) projects Project[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("utilization_categories") } // ─── Client / WBS ─────────────────────────────────────────────────────────── model Client { id String @id @default(cuid()) name String code String? @unique // optional short code e.g. "BMW" parentId String? parent Client? @relation("ClientTree", fields: [parentId], references: [id]) children Client[] @relation("ClientTree") isActive Boolean @default(true) sortOrder Int @default(0) projects Project[] resourceClientUnits Resource[] @relation("resource_client_unit") rateCards RateCard[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([parentId, name]) @@map("clients") } // ─── Management Level ─────────────────────────────────────────────────────── model ManagementLevelGroup { id String @id @default(cuid()) name String @unique targetPercentage Float // e.g. 0.805 for 80.5% sortOrder Int @default(0) levels ManagementLevel[] resources Resource[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("management_level_groups") } model ManagementLevel { id String @id @default(cuid()) name String @unique // e.g. "10-Senior Analyst" groupId String group ManagementLevelGroup @relation(fields: [groupId], references: [id]) resources Resource[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("management_levels") } // ─── Blueprint System ───────────────────────────────────────────────────────── model Blueprint { id String @id @default(cuid()) name String target BlueprintTarget description String? // fieldDefs: BlueprintFieldDefinition[] (see shared/types) fieldDefs Json @db.JsonB // defaults: Record defaults Json @db.JsonB @default("{}") // validationRules: BlueprintValidationRule[] validationRules Json @db.JsonB @default("[]") // rolePresets: StaffingRequirement[] — default roles for project creation wizard rolePresets Json @db.JsonB @default("[]") isActive Boolean @default(true) isGlobal Boolean @default(false) resources Resource[] projects Project[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("blueprints") } // ─── Role ───────────────────────────────────────────────────────────────────── model Role { id String @id @default(cuid()) name String @unique description String? color String? // hex color e.g. "#6366f1" isActive Boolean @default(true) resourceRoles ResourceRole[] demandRequirements DemandRequirement[] assignments Assignment[] areaResources Resource[] @relation("resource_area_role") estimateDemandLines EstimateDemandLine[] @relation("estimate_demand_line_role") rateCardLines RateCardLine[] @relation("rate_card_line_role") resourceCostSnapshots ResourceCostSnapshot[] @relation("resource_cost_snapshot_role") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("roles") } // ─── ResourceRole ───────────────────────────────────────────────────────────── model ResourceRole { id String @id @default(cuid()) resourceId String roleId String isPrimary Boolean @default(false) resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) role Role @relation(fields: [roleId], references: [id], onDelete: Restrict) @@unique([resourceId, roleId]) @@map("resource_roles") } // ─── Resource ───────────────────────────────────────────────────────────────── model Resource { id String @id @default(cuid()) eid String @unique // Employee ID, e.g. "EMP-001" displayName String email String @unique chapter String? // Monetary values in integer cents (e.g. 8500 = 85.00 EUR/h) lcrCents Int // Labor Cost Rate (internal) ucrCents Int // Utilization Cost Rate (charge-out) currency String @default("EUR") chargeabilityTarget Float @default(80) // Percent 0-100 // availability: WeekdayAvailability availability Json @db.JsonB // skills: SkillEntry[] skills Json @db.JsonB @default("[]") // dynamicFields: Record dynamicFields Json @db.JsonB @default("{}") blueprintId String? blueprint Blueprint? @relation(fields: [blueprintId], references: [id]) isActive Boolean @default(true) userId String? @unique user User? @relation(fields: [userId], references: [id]) // Skill Matrix fields portfolioUrl String? roleId String? aiSummary String? aiSummaryUpdatedAt DateTime? skillMatrixUpdatedAt DateTime? areaRole Role? @relation("resource_area_role", fields: [roleId], references: [id], onDelete: SetNull) // Value Score fields valueScore Int? valueScoreBreakdown Json? @db.JsonB valueScoreUpdatedAt DateTime? // Vacation-related postalCode String? federalState String? // e.g. "BY" (Bavaria), derived from postalCode or set manually // Dispo v2: Country / Metro City / Org Unit countryId String? country Country? @relation(fields: [countryId], references: [id]) metroCityId String? metroCity MetroCity? @relation(fields: [metroCityId], references: [id]) orgUnitId String? orgUnit OrgUnit? @relation(fields: [orgUnitId], references: [id]) // Dispo v2: Management Level managementLevelGroupId String? managementLevelGroup ManagementLevelGroup? @relation(fields: [managementLevelGroupId], references: [id]) managementLevelId String? managementLevel ManagementLevel? @relation(fields: [managementLevelId], references: [id]) // Dispo v2: Resource classification resourceType ResourceType @default(EMPLOYEE) chgResponsibility Boolean @default(true) // "Chg Responsibility" status rolledOff Boolean @default(false) departed Boolean @default(false) enterpriseId String? @unique // Enterprise ID e.g. "a.kasperovich" (username, not email) clientUnitId String? clientUnit Client? @relation("resource_client_unit", fields: [clientUnitId], references: [id]) fte Float @default(1.0) // at least 2 decimal places assignments Assignment[] vacations Vacation[] resourceRoles ResourceRole[] vacationEntitlements VacationEntitlement[] estimateDemandLines EstimateDemandLine[] @relation("estimate_demand_line_resource") resourceCostSnapshots ResourceCostSnapshot[] @relation("resource_cost_snapshot_resource") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([chapter]) @@index([isActive]) @@index([countryId]) @@index([orgUnitId]) @@index([resourceType]) @@map("resources") } // ─── Project ────────────────────────────────────────────────────────────────── model Project { id String @id @default(cuid()) shortCode String @unique // e.g. "PROJ-001" name String orderType OrderType allocationType AllocationType winProbability Int @default(100) // 0-100 budgetCents Int // Total budget in cents startDate DateTime @db.Date endDate DateTime @db.Date status ProjectStatus @default(DRAFT) responsiblePerson String? // staffingReqs: StaffingRequirement[] staffingReqs Json @db.JsonB @default("[]") // dynamicFields: Record dynamicFields Json @db.JsonB @default("{}") blueprintId String? blueprint Blueprint? @relation(fields: [blueprintId], references: [id]) // Dispo v2: Utilization Category + Client utilizationCategoryId String? utilizationCategory UtilizationCategory? @relation(fields: [utilizationCategoryId], references: [id]) clientId String? client Client? @relation(fields: [clientId], references: [id]) demandRequirements DemandRequirement[] assignments Assignment[] estimates Estimate[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status]) @@index([startDate, endDate]) @@index([orderType]) @@index([clientId]) @@map("projects") } // ─── Estimating ────────────────────────────────────────────────────────────── model Estimate { id String @id @default(cuid()) projectId String? name String opportunityId String? baseCurrency String @default("EUR") status EstimateStatus @default(DRAFT) latestVersionNumber Int @default(1) project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull) versions EstimateVersion[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([projectId]) @@index([status]) @@index([opportunityId]) @@map("estimates") } model EstimateVersion { id String @id @default(cuid()) estimateId String versionNumber Int label String? status EstimateVersionStatus @default(WORKING) notes String? lockedAt DateTime? projectSnapshot Json @db.JsonB @default("{}") commercialTerms Json? @db.JsonB estimate Estimate @relation(fields: [estimateId], references: [id], onDelete: Cascade) assumptions EstimateAssumption[] scopeItems ScopeItem[] demandLines EstimateDemandLine[] resourceSnapshots ResourceCostSnapshot[] metrics EstimateMetric[] exports EstimateExport[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([estimateId, versionNumber]) @@index([status]) @@map("estimate_versions") } model EstimateAssumption { id String @id @default(cuid()) estimateVersionId String category String key String label String valueType String @default("json") value Json @db.JsonB sortOrder Int @default(0) notes String? estimateVersion EstimateVersion @relation(fields: [estimateVersionId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([estimateVersionId, category, key]) @@index([estimateVersionId, sortOrder]) @@map("estimate_assumptions") } model ScopeItem { id String @id @default(cuid()) estimateVersionId String sequenceNo Int scopeType String packageCode String? name String description String? scene String? page String? location String? assumptionCategory String? technicalSpec Json @db.JsonB @default("{}") frameCount Int? itemCount Float? unitMode String? internalComments String? externalComments String? sortOrder Int @default(0) metadata Json @db.JsonB @default("{}") estimateVersion EstimateVersion @relation(fields: [estimateVersionId], references: [id], onDelete: Cascade) demandLines EstimateDemandLine[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([estimateVersionId, sortOrder]) @@index([estimateVersionId, sequenceNo]) @@map("scope_items") } model EstimateDemandLine { id String @id @default(cuid()) estimateVersionId String scopeItemId String? roleId String? resourceId String? lineType String @default("LABOR") name String chapter String? hours Float days Float? fte Float? rateSource String? costRateCents Int @default(0) billRateCents Int @default(0) currency String @default("EUR") costTotalCents Int @default(0) priceTotalCents Int @default(0) monthlySpread Json @db.JsonB @default("{}") staffingAttributes Json @db.JsonB @default("{}") metadata Json @db.JsonB @default("{}") estimateVersion EstimateVersion @relation(fields: [estimateVersionId], references: [id], onDelete: Cascade) scopeItem ScopeItem? @relation(fields: [scopeItemId], references: [id], onDelete: SetNull) role Role? @relation("estimate_demand_line_role", fields: [roleId], references: [id], onDelete: SetNull) resource Resource? @relation("estimate_demand_line_resource", fields: [resourceId], references: [id], onDelete: SetNull) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([estimateVersionId]) @@index([scopeItemId]) @@index([roleId]) @@index([resourceId]) @@map("estimate_demand_lines") } model RateCard { id String @id @default(cuid()) name String currency String @default("EUR") effectiveFrom DateTime? @db.Date effectiveTo DateTime? @db.Date source String? isActive Boolean @default(true) clientId String? client Client? @relation(fields: [clientId], references: [id], onDelete: SetNull) lines RateCardLine[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([isActive]) @@index([clientId]) @@index([effectiveFrom, effectiveTo]) @@map("rate_cards") } model RateCardLine { id String @id @default(cuid()) rateCardId String roleId String? chapter String? location String? seniority String? workType String? serviceGroup String? costRateCents Int billRateCents Int? machineRateCents Int? attributes Json @db.JsonB @default("{}") rateCard RateCard @relation(fields: [rateCardId], references: [id], onDelete: Cascade) role Role? @relation("rate_card_line_role", fields: [roleId], references: [id], onDelete: SetNull) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([rateCardId]) @@index([roleId]) @@map("rate_card_lines") } model ResourceCostSnapshot { id String @id @default(cuid()) estimateVersionId String resourceId String? sourceEid String? displayName String chapter String? roleId String? currency String @default("EUR") lcrCents Int ucrCents Int fte Float? location String? country String? level String? workType String? attributes Json @db.JsonB @default("{}") estimateVersion EstimateVersion @relation(fields: [estimateVersionId], references: [id], onDelete: Cascade) resource Resource? @relation("resource_cost_snapshot_resource", fields: [resourceId], references: [id], onDelete: SetNull) role Role? @relation("resource_cost_snapshot_role", fields: [roleId], references: [id], onDelete: SetNull) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([estimateVersionId]) @@index([resourceId]) @@map("resource_cost_snapshots") } model EstimateMetric { id String @id @default(cuid()) estimateVersionId String key String label String metricGroup String? valueDecimal Float valueCents Int? currency String? metadata Json @db.JsonB @default("{}") estimateVersion EstimateVersion @relation(fields: [estimateVersionId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([estimateVersionId, key]) @@index([estimateVersionId, metricGroup]) @@map("estimate_metrics") } model EstimateExport { id String @id @default(cuid()) estimateVersionId String format EstimateExportFormat fileName String storageKey String? payload Json? @db.JsonB estimateVersion EstimateVersion @relation(fields: [estimateVersionId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([estimateVersionId, format]) @@map("estimate_exports") } model EffortRuleSet { id String @id @default(cuid()) name String description String? isDefault Boolean @default(false) rules EffortRule[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([isDefault]) @@map("effort_rule_sets") } model EffortRule { id String @id @default(cuid()) ruleSetId String scopeType String discipline String chapter String? unitMode String @default("flat") hoursPerUnit Float description String? sortOrder Int @default(0) ruleSet EffortRuleSet @relation(fields: [ruleSetId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([ruleSetId]) @@index([scopeType]) @@map("effort_rules") } // ─── Experience Multipliers ────────────────────────────────────────────────── model ExperienceMultiplierSet { id String @id @default(cuid()) name String description String? isDefault Boolean @default(false) rules ExperienceMultiplierRule[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([isDefault]) @@map("experience_multiplier_sets") } model ExperienceMultiplierRule { id String @id @default(cuid()) multiplierSetId String chapter String? location String? level String? costMultiplier Float @default(1.0) billMultiplier Float @default(1.0) shoringRatio Float? additionalEffortRatio Float? description String? sortOrder Int @default(0) multiplierSet ExperienceMultiplierSet @relation(fields: [multiplierSetId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([multiplierSetId]) @@map("experience_multiplier_rules") } // ─── DemandRequirement / Assignment (additive migration target) ───────────── model DemandRequirement { id String @id @default(cuid()) projectId String startDate DateTime @db.Date endDate DateTime @db.Date hoursPerDay Float percentage Float role String? roleId String? headcount Int @default(1) status AllocationStatus @default(PROPOSED) metadata Json @db.JsonB @default("{}") project Project @relation(fields: [projectId], references: [id]) roleEntity Role? @relation(fields: [roleId], references: [id]) assignments Assignment[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([projectId]) @@index([startDate, endDate]) @@index([status]) @@index([projectId, status, startDate]) @@map("demand_requirements") } model Assignment { id String @id @default(cuid()) demandRequirementId String? resourceId String projectId String startDate DateTime @db.Date endDate DateTime @db.Date hoursPerDay Float percentage Float role String? roleId String? dailyCostCents Int status AllocationStatus @default(PROPOSED) metadata Json @db.JsonB @default("{}") demandRequirement DemandRequirement? @relation(fields: [demandRequirementId], references: [id]) resource Resource @relation(fields: [resourceId], references: [id]) project Project @relation(fields: [projectId], references: [id]) roleEntity Role? @relation(fields: [roleId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([resourceId, projectId, startDate, endDate], name: "unique_assignment") @@index([demandRequirementId]) @@index([resourceId]) @@index([projectId]) @@index([startDate, endDate]) @@index([status]) @@index([resourceId, status, startDate]) @@index([projectId, startDate, endDate]) @@map("assignments") } // ─── Vacation ───────────────────────────────────────────────────────────────── model Vacation { id String @id @default(cuid()) resourceId String type VacationType status VacationStatus @default(PENDING) startDate DateTime @db.Date endDate DateTime @db.Date note String? rejectionReason String? isHalfDay Boolean @default(false) halfDayPart String? // "MORNING" | "AFTERNOON" requestedById String approvedById String? approvedAt DateTime? resource Resource @relation(fields: [resourceId], references: [id]) requestedBy User @relation("vacation_requested", fields: [requestedById], references: [id]) approvedBy User? @relation("vacation_approved", fields: [approvedById], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([resourceId]) @@index([startDate, endDate]) @@index([status]) @@index([resourceId, status, startDate, endDate]) @@map("vacations") } // ─── VacationEntitlement ────────────────────────────────────────────────────── model VacationEntitlement { id String @id @default(cuid()) resourceId String year Int entitledDays Float @default(28) // annual quota (including carryover) carryoverDays Float @default(0) // days carried over from previous year usedDays Float @default(0) // APPROVED ANNUAL+OTHER days consumed (cached) pendingDays Float @default(0) // PENDING ANNUAL+OTHER days (cached) resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([resourceId, year]) @@index([year]) @@map("vacation_entitlements") } // ─── Notification ───────────────────────────────────────────────────────────── model Notification { id String @id @default(cuid()) userId String type String // e.g. "VACATION_REQUESTED", "OVERALLOCATION", "PROJECT_DEADLINE" title String body String? entityId String? // related entity (vacation ID, allocation ID, project ID) entityType String? // "vacation", "allocation", "project" readAt DateTime? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, readAt]) @@map("notifications") } // ─── System Settings ────────────────────────────────────────────────────────── model SystemSettings { id String @id @default("singleton") aiProvider String? @default("openai") // "openai" | "azure" azureOpenAiEndpoint String? // Azure only: base URL e.g. "https://myinstance.cognitiveservices.azure.com" azureOpenAiDeployment String? // deployment/model name e.g. "gpt-5" azureOpenAiApiKey String? // API key (plaintext, internal only) azureApiVersion String? @default("2025-01-01-preview") // Azure API version aiMaxCompletionTokens Int? @default(2000) aiTemperature Float? @default(1) aiSummaryPrompt String? // prompt template; use {role} {chapter} {mainSkills} {topSkills} scoreWeights Json? @db.JsonB // ValueScoreWeights (5 floats summing to 1.0) scoreVisibleRoles Json? @db.JsonB // SystemRole[] e.g. ["ADMIN","MANAGER"] // SMTP / email settings smtpHost String? smtpPort Int? @default(587) smtpUser String? smtpPassword String? smtpFrom String? smtpTls Boolean? @default(true) // Global viewer-side anonymization anonymizationEnabled Boolean? @default(false) anonymizationDomain String? @default("superhartmut.de") anonymizationSeed String? anonymizationMode String? @default("global") anonymizationAliases Json? @db.JsonB // Vacation defaults vacationDefaultDays Int? @default(28) // default annual entitlement updatedAt DateTime @updatedAt @@map("system_settings") } // ─── Audit Log ──────────────────────────────────────────────────────────────── model AuditLog { id String @id @default(cuid()) entityType String // "Resource" | "Project" | "Allocation" | "Blueprint" entityId String action AuditAction userId String? user User? @relation(fields: [userId], references: [id]) // changes: { before: Record, after: Record } changes Json @db.JsonB createdAt DateTime @default(now()) @@index([entityType, entityId]) @@index([userId]) @@index([createdAt]) @@map("audit_logs") }