// CapaKraken — 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 HolidayCalendarScope {
  COUNTRY
  STATE
  CITY
}

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 AbsenceTrigger {
  SICK
  VACATION
  PUBLIC_HOLIDAY
  CUSTOM
}

enum CostEffect {
  CHARGE
  ZERO
  REDUCE
}

enum ChargeabilityEffect {
  COUNT
  SKIP
}

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
  favoriteProjectIds Json?     @db.JsonB  // string[] of project IDs
  lastLoginAt        DateTime?
  lastActiveAt       DateTime?
  totpSecret         String?           // Base32 TOTP secret
  totpEnabled        Boolean  @default(false)
  isActive           Boolean  @default(true)
  deletedAt          DateTime?

  accounts          Account[]
  sessions          Session[]
  auditLogs         AuditLog[]
  vacationsRequested Vacation[] @relation("vacation_requested")
  vacationsApproved  Vacation[] @relation("vacation_approved")
  resource           Resource?
  notifications      Notification[]
  tasksAssigned      Notification[]          @relation("taskAssignee")
  notificationsSent  Notification[]          @relation("notificationSender")
  broadcasts         NotificationBroadcast[] @relation("broadcastSender")
  comments           Comment[]
  activeSessions     ActiveSession[]
  reportTemplates    ReportTemplate[]
  assistantApprovals AssistantApproval[]

  createdAt         DateTime  @default(now())
  updatedAt         DateTime  @updatedAt

  @@map("users")
}

enum AssistantApprovalStatus {
  PENDING
  APPROVED
  CANCELLED
  EXPIRED
}

enum ReportTemplateEntity {
  RESOURCE
  PROJECT
  ASSIGNMENT
  RESOURCE_MONTH
}

model ReportTemplate {
  id          String               @id @default(cuid())
  ownerId     String
  name        String
  description String?
  entity      ReportTemplateEntity
  config      Json                 @db.JsonB
  isShared    Boolean              @default(false)

  owner       User                 @relation(fields: [ownerId], references: [id], onDelete: Cascade)

  createdAt   DateTime             @default(now())
  updatedAt   DateTime             @updatedAt

  @@index([ownerId, updatedAt])
  @@unique([ownerId, name])
  @@map("report_templates")
}

model AssistantApproval {
  id             String                  @id @default(cuid())
  userId         String
  conversationId String
  toolName       String
  toolArguments  String                  @db.Text
  summary        String
  status         AssistantApprovalStatus @default(PENDING)
  approvedAt     DateTime?
  cancelledAt    DateTime?
  expiresAt      DateTime
  createdAt      DateTime                @default(now())
  updatedAt      DateTime                @updatedAt

  user           User                    @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId, conversationId, status, expiresAt])
  @@index([status, expiresAt])
  @@map("assistant_approvals")
}

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[]
  holidayCalendars  HolidayCalendar[]
  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[]
  holidayCalendars HolidayCalendar[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@unique([countryId, name])
  @@map("metro_cities")
}

model HolidayCalendar {
  id          String               @id @default(cuid())
  name        String
  scopeType   HolidayCalendarScope
  countryId   String
  stateCode   String?
  metroCityId String?
  isActive    Boolean              @default(true)
  priority    Int                  @default(0)
  createdAt   DateTime             @default(now())
  updatedAt   DateTime             @updatedAt

  country     Country              @relation(fields: [countryId], references: [id], onDelete: Cascade)
  metroCity   MetroCity?           @relation(fields: [metroCityId], references: [id], onDelete: Cascade)
  entries     HolidayCalendarEntry[]

  @@index([countryId, scopeType])
  @@index([countryId, stateCode])
  @@index([metroCityId])
  // Scope uniqueness is enforced via partial unique indexes in SQL migrations.
  @@map("holiday_calendars")
}

model HolidayCalendarEntry {
  id                String          @id @default(cuid())
  holidayCalendarId String
  date              DateTime        @db.Date
  name              String
  isRecurringAnnual Boolean         @default(false)
  source            String?
  createdAt         DateTime        @default(now())
  updatedAt         DateTime        @updatedAt

  holidayCalendar   HolidayCalendar @relation(fields: [holidayCalendarId], references: [id], onDelete: Cascade)

  @@unique([holidayCalendarId, date])
  @@index([date])
  @@map("holiday_calendar_entries")
}

// ─── 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)
  deletedAt DateTime?
  sortOrder Int       @default(0)
  tags      String[]  @default([])

  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

  @@index([groupId])
  @@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<string, unknown>
  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)
  deletedAt       DateTime?
  isGlobal        Boolean         @default(false)

  resources       Resource[]
  projects        Project[]

  createdAt       DateTime        @default(now())
  updatedAt       DateTime        @updatedAt

  @@index([isActive])
  @@index([target])
  @@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)
  deletedAt     DateTime?

  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<string, unknown>
  dynamicFields       Json      @db.JsonB @default("{}")

  blueprintId         String?
  blueprint           Blueprint? @relation(fields: [blueprintId], references: [id])
  isActive            Boolean   @default(true)
  deletedAt           DateTime?

  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])
  @@index([managementLevelGroupId])
  @@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?
  shoringThreshold   Int?              @default(55)  // Max offshore % before alert (0-100)
  onshoreCountryCode String?           @default("DE") // Country code considered "onshore"
  color             String?           // Hex color for timeline display, e.g. "#3b82f6"
  coverImageUrl     String?  @db.Text // Base64 data-URL for project cover art
  coverFocusY       Int      @default(50) // Vertical focus point 0-100 (% from top)

  // staffingReqs: StaffingRequirement[]
  staffingReqs    Json           @db.JsonB @default("[]")
  // dynamicFields: Record<string, unknown>
  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[]
  calculationRules CalculationRule[]

  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)
  budgetCents        Int              @default(0)
  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("{}")
  overbookingAcknowledged Boolean           @default(false)

  demandRequirement   DemandRequirement? @relation(fields: [demandRequirementId], references: [id], onDelete: SetNull)
  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])
  @@index([status, startDate, endDate])
  @@index([projectId, status, startDate, endDate])
  @@index([resourceId, status, endDate])
  @@index([projectId, status, 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"
  deductedDays   Float?
  holidayCountryCode String?
  holidayCountryName String?
  holidayFederalState String?
  holidayMetroCityName String?
  holidayCalendarDates Json?    @db.JsonB
  holidayLegacyPublicHolidayDates Json? @db.JsonB
  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])
  @@index([requestedById])
  @@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 Enums ──────────────────────────────────────────────────────

enum NotificationCategory {
  NOTIFICATION
  REMINDER
  TASK
  APPROVAL
}

enum NotificationPriority {
  LOW
  NORMAL
  HIGH
  URGENT
}

enum TaskStatus {
  OPEN
  IN_PROGRESS
  DONE
  DISMISSED
}

// ─── 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())

  // Extended fields
  category   NotificationCategory @default(NOTIFICATION)
  priority   NotificationPriority @default(NORMAL)
  link       String?

  // Task fields
  taskStatus   TaskStatus?
  taskAction   String?
  assigneeId   String?
  dueDate      DateTime?
  completedAt  DateTime?
  completedBy  String?

  // Reminder fields
  remindAt     DateTime?
  recurrence   String?      // "daily" | "weekly" | "monthly" | null
  nextRemindAt DateTime?

  // Targeting
  sourceId     String?
  senderId     String?
  channel      String  @default("in_app")

  updatedAt    DateTime @updatedAt

  // Relations
  user     User  @relation(fields: [userId], references: [id], onDelete: Cascade)
  assignee User? @relation("taskAssignee", fields: [assigneeId], references: [id])
  sender   User? @relation("notificationSender", fields: [senderId], references: [id])

  @@index([userId, readAt])
  @@index([userId, category, taskStatus])
  @@index([nextRemindAt])
  @@index([assigneeId, taskStatus])
  @@index([category, nextRemindAt])
  @@index([userId, dueDate])
  @@map("notifications")
}

// ─── Notification Broadcast ─────────────────────────────────────────────────

model NotificationBroadcast {
  id             String               @id @default(cuid())
  senderId       String
  title          String
  body           String?
  link           String?
  category       NotificationCategory @default(NOTIFICATION)
  priority       NotificationPriority @default(NORMAL)
  channel        String               @default("in_app")

  targetType     String   // "user" | "role" | "project" | "orgUnit" | "all"
  targetValue    String?

  scheduledAt    DateTime?
  sentAt         DateTime?
  recipientCount Int @default(0)

  createdAt      DateTime @default(now())

  sender User @relation("broadcastSender", fields: [senderId], references: [id])

  @@index([senderId])
  @@index([scheduledAt, sentAt])
  @@map("notification_broadcasts")
}

// ─── System Role Configuration ────────────────────────────────────────────────

model SystemRoleConfig {
  role              SystemRole @id
  label             String     // Display label, e.g. "Manager"
  description       String?    // Optional description of the role
  defaultPermissions Json      @db.JsonB  // PermissionKey[] — default permissions for this role
  color             String?    // Badge color, e.g. "purple", "blue"
  sortOrder         Int        @default(0)

  createdAt         DateTime   @default(now())
  updatedAt         DateTime   @updatedAt

  @@map("system_role_configs")
}

// ─── 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
  // Timeline undo
  timelineUndoMaxSteps    Int?     @default(50)  // max undo history depth
  // DALL-E image generation (Azure requires separate deployment)
  azureDalleDeployment    String?  // e.g. "dall-e-3" — Azure DALL-E deployment name
  azureDalleEndpoint      String?  // Optional: separate endpoint for DALL-E (if different from chat)
  azureDalleApiKey        String?  // Optional: separate API key for DALL-E
  // Gemini image generation
  geminiApiKey            String?
  geminiModel             String?  @default("gemini-2.5-flash-image")
  imageProvider           String?  @default("dalle")  // "dalle" | "gemini"
  // Session timeout settings
  sessionMaxAge           Int?     @default(28800)    // Absolute timeout in seconds (8h)
  sessionIdleTimeout      Int?     @default(1800)     // Idle timeout in seconds (30min)
  // Concurrent session limit (kick-oldest strategy)
  maxConcurrentSessions   Int?     @default(3)
  // MFA enforcement: roles that are required to have TOTP enabled (JSON array of SystemRole strings)
  // e.g. ["ADMIN"] or ["ADMIN","MANAGER"]. Null = no enforcement.
  requireMfaForRoles      Json?    @db.JsonB
  updatedAt               DateTime @updatedAt

  @@map("system_settings")
}

// ─── Active Session Registry (JWT session tracking) ──────────────────────────

model ActiveSession {
  id          String   @id @default(cuid())
  userId      String
  jti         String   @unique  // JWT ID — unique per token
  createdAt   DateTime @default(now())
  lastSeenAt  DateTime @default(now())
  userAgent   String?
  ipAddress   String?

  user        User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId, createdAt])
  @@map("active_sessions")
}

// ─── Calculation Rules ────────────────────────────────────────────────────────

model CalculationRule {
  id                    String               @id @default(cuid())
  name                  String
  description           String?

  // ── Matching ──
  triggerType           AbsenceTrigger
  projectId             String?
  orderType             OrderType?

  // ── Effects ──
  costEffect            CostEffect
  costReductionPercent  Int?                 // only for REDUCE (0-100)
  chargeabilityEffect   ChargeabilityEffect

  // ── Ordering ──
  priority              Int                  @default(0)
  isActive              Boolean              @default(true)

  project               Project?             @relation(fields: [projectId], references: [id])

  createdAt             DateTime             @default(now())
  updatedAt             DateTime             @updatedAt

  @@index([triggerType, isActive])
  @@index([projectId])
  @@map("calculation_rules")
}

// ─── Comment ─────────────────────────────────────────────────────────────────

model Comment {
  id            String    @id @default(cuid())
  entityType    String    // "estimate", "estimate_version", "scope_item", "demand_line"
  entityId      String
  parentId      String?   // for replies
  authorId      String
  body          String    @db.Text
  mentions      String[]  // user IDs mentioned
  resolved      Boolean   @default(false)
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt

  author        User      @relation(fields: [authorId], references: [id])
  parent        Comment?  @relation("CommentReplies", fields: [parentId], references: [id])
  replies       Comment[] @relation("CommentReplies")

  @@index([entityType, entityId])
  @@index([authorId])
  @@index([parentId])
  @@map("comments")
}

// ─── 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<string, unknown>, after: Record<string, unknown> }
  changes    Json        @db.JsonB
  createdAt  DateTime    @default(now())
  source     String?     // "ui" | "api" | "ai" | "import" | "cron"
  entityName String?     // Human-readable name e.g. "Porsche Taycan Project"
  summary    String?     // One-liner: "Changed status from DRAFT to ACTIVE"

  @@index([entityType, entityId])
  @@index([userId])
  @@index([createdAt])
  @@index([entityType, createdAt])
  @@map("audit_logs")
}

// ─── Webhook ──────────────────────────────────────────────────────────────────

model Webhook {
  id        String   @id @default(cuid())
  name      String
  url       String
  secret    String?  // HMAC signing secret
  events    String[] // ["allocation.created", "project.status_changed", etc.]
  isActive  Boolean  @default(true)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("webhooks")
}

// ─── Invite Token ─────────────────────────────────────────────────────────────

model InviteToken {
  id          String     @id @default(cuid())
  email       String
  role        SystemRole @default(USER)
  token       String     @unique
  expiresAt   DateTime
  usedAt      DateTime?
  createdById String     // userId of the inviting admin
  createdAt   DateTime   @default(now())

  @@index([token])
  @@index([email])
  @@map("invite_tokens")
}

model PasswordResetToken {
  id        String    @id @default(cuid())
  email     String
  token     String    @unique
  expiresAt DateTime
  usedAt    DateTime?
  createdAt DateTime  @default(now())

  @@index([token])
  @@index([email])
  @@map("password_reset_tokens")
}
