# 绩效管理模块 - 数据模型文档

> **module**: performance
> **doc_type**: DataModel
> **status**: Active
> **owner**: FFOA 开发团队
> **upstream_docs**: 01-prd.md, 03-architecture.md
> **last_verified**: 2026-03-20

---

### Schema 摘要

| 字段 | 内容 |
|------|------|
| Schema 名称 | `platform_performance` |
| 业务域 | 绩效管理（KPI、360 评估、校准、结果、战略目标） |
| 核心实体 | PerformanceCycle, GradeConfig, PerformanceResult, KpiAssignment, KpiDependency, KpiAssessment, Evaluation360, Evaluation360Template, EvaluationTask, EvaluationResponse, GradeAdjustmentLog, StrategicObjective, StrategicObjectiveAssignment |

### 实体字段清单

#### PerformanceCycle（绩效周期）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| name | String | ✅ | 周期名称 |
| type | Enum | ✅ | 周期类型（MONTHLY/QUARTERLY/SEMI_ANNUAL/ANNUAL/CUSTOM） |
| status | Enum | ✅ | 状态（DRAFT/GOAL_SETTING/IN_PROGRESS/EVALUATING/CALIBRATING/CONFIRMING/COMPLETED/ARCHIVED） |
| startDate | DateTime | ✅ | 开始日期 |
| endDate | DateTime | ✅ | 结束日期 |
| gradeConfigId | UUID | ❌ | 绩效等级配置 ID（引用 GradeConfig） |
| resultsPublishedAt | DateTime | ❌ | 结果发布时间 |
| resultsPublishedBy | UUID | ❌ | 结果发布人 |
| parentCycleId | UUID | ❌ | 父周期 ID（支持子周期结构） |
| organizationId | UUID | ✅ | 所属组织 ID（强约束，周期必须归属组织） |
| createdBy | UUID | ✅ | 创建人 |
| createdAt | DateTime | ✅ | 创建时间 |
| updatedAt | DateTime | ✅ | 更新时间 |
| deletedAt | DateTime | ❌ | 软删除时间 |

#### GradeConfig（绩效等级配置）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| name | String | ✅ | 配置名称（如"五级制"、"四级制"） |
| description | String | ❌ | 配置描述 |
| grades | JSON | ✅ | 等级定义数组（应用层保证：代码唯一、区间不重叠、覆盖 0-100）（见下方结构） |
| isDefault | Boolean | ✅ | 是否为默认配置 |
| isActive | Boolean | ✅ | 是否启用 |
| organizationId | UUID | ❌ | 所属组织 ID（null = 平台级配置） |
| createdBy | UUID | ✅ | 创建人 |
| createdAt | DateTime | ✅ | 创建时间 |
| updatedAt | DateTime | ✅ | 更新时间 |
| deletedAt | DateTime | ❌ | 软删除时间 |

**grades JSON 结构**（颜色以前端定稿为准）：
```json
[
  { "code": "S", "name": "卓越", "minScore": 90, "maxScore": 100, "color": "#8B5CF6", "order": 1 },
  { "code": "A", "name": "优秀", "minScore": 80, "maxScore": 89, "color": "#3B82F6", "order": 2 },
  { "code": "B", "name": "良好", "minScore": 70, "maxScore": 79, "color": "#10B981", "order": 3 },
  { "code": "C", "name": "达标", "minScore": 60, "maxScore": 69, "color": "#F97316", "order": 4 },
  { "code": "D", "name": "待提升", "minScore": 0, "maxScore": 59, "color": "#EF4444", "order": 5 }
]
```

#### PerformanceResult（绩效结果）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| cycleId | UUID | ✅ | 所属周期 |
| employeeId | UUID | ✅ | 员工 ID |
| kpiScore | Decimal | ❌ | KPI 综合得分 |
| kpiWeight | Decimal | ❌ | KPI 权重占比 |
| e360Score | Decimal | ❌ | 360 评估综合得分 |
| e360Weight | Decimal | ❌ | 360 评估权重占比 |
| totalScore | Decimal | ❌ | 综合总分（KPI/360 权重汇总），结果生成时填充 |
| proposedGradeCode | String | ❌ | 汇总分数匹配的原始等级代码，结果生成时填充 |
| proposedGradeName | String | ❌ | 原始等级名称，结果生成时填充 |
| gradeCode | String | ❌ | 最终等级代码（如 S/A/B/C/D），结果生成时填充 |
| gradeName | String | ❌ | 最终等级名称（如"卓越"/"优秀"），结果生成时填充 |
| isPublished | Boolean | ✅ | 是否已发布给员工 |
| publishedAt | DateTime | ❌ | 发布时间 |
| viewedByEmployee | Boolean | ✅ | 员工是否已查看 |
| viewedAt | DateTime | ❌ | 员工查看时间 |
| confirmStatus | Enum | ✅ | 确认状态（PENDING/CONFIRMED/APPEALED/APPEAL_RESOLVED） |
| appealReason | String | ❌ | 申诉原因 |
| appealResponse | String | ❌ | HR 申诉回复 |
| appealResolvedAt | DateTime | ❌ | 申诉处理时间 |
| selfOverallComment | String | ❌ | 员工整体自评 |
| managerOverallComment | String | ❌ | 经理整体评语 |
| remarks | String | ❌ | HR 备注 |
| organizationId | UUID | ✅ | 冗余组织 ID（简化分析查询） |
| createdAt | DateTime | ✅ | 创建时间 |
| updatedAt | DateTime | ✅ | 更新时间 |
| deletedAt | DateTime | ❌ | 软删除时间 |

> **PerformanceResult 生命周期**：该记录可能在两个时机创建：
> 1. **EVALUATING 阶段**：员工/经理保存整体评语时，API 自动 upsert（仅填充 cycleId、employeeId、organizationId 和评语字段，分数和等级字段为空）。
> 2. **进入 CALIBRATING 时**：`ensurePerformanceResults` 自动 upsert 补齐分数和等级，不会覆盖已有评语。
>
> 因此 totalScore、proposedGradeCode、gradeCode 等字段在数据库层为可空（允许评语先于分数存在），但在结果正式生成后保证有值。

#### KpiAssignment（KPI 分配）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| cycleId | UUID | ✅ | 所属周期 |
| employeeId | UUID | ✅ | 员工 ID |
| name | String | ✅ | KPI 名称（必填） |
| description | String | ❌ | KPI 描述 |
| weight | Decimal | ✅ | 权重 |
| unit | String | ❌ | 单位 |
| baseTarget | String | ❌ | 基本目标 |
| stretchTarget | String | ❌ | 挑战目标 |
| targetValue | Decimal | ❌ | 目标值（数值型） |
| parentId | UUID | ❌ | 父 KPI ID（支持 KPI 层级结构） |
| seq | Int | ❌ | 排序序号 |
| status | Enum | ✅ | 审批状态（DRAFT/SUBMITTED/APPROVED/REJECTED） |
| maturityScore | Decimal | ❌ | 成熟度评分 |
| createdAt | DateTime | ✅ | 创建时间 |
| updatedAt | DateTime | ✅ | 更新时间 |
| deletedAt | DateTime | ❌ | 软删除时间 |

#### KpiDependency（KPI 跨部门依赖）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| sourceAssignmentId | UUID | ✅ | 发起依赖的 KPI 分配 ID |
| targetAssignmentId | UUID | ❌ | 被依赖的 KPI 分配 ID（对方可能尚未创建） |
| targetUserId | UUID | ✅ | 被依赖的用户 ID |
| description | String | ❌ | 依赖描述 |
| status | Enum | ✅ | 依赖状态（PENDING/CONFIRMED/REJECTED） |
| rejectionReason | String | ❌ | 拒绝原因 |
| confirmedAt | DateTime | ❌ | 确认时间 |
| createdAt | DateTime | ✅ | 创建时间 |
| updatedAt | DateTime | ✅ | 更新时间 |
| deletedAt | DateTime | ❌ | 软删除时间 |

#### KpiAssessment（KPI 考核记录）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| assignmentId | UUID | ✅ | 关联分配 |
| status | Enum | ✅ | 状态（PENDING/SELF_EVALUATED/MANAGER_EVALUATED/CONFIRMED） |
| selfScore | Decimal | ❌ | 自评分数 |
| completionNote | String | ❌ | 完成情况说明 |
| selfComment | String | ❌ | 自评说明 |
| selfEvaluatedAt | DateTime | ❌ | 自评时间 |
| managerScore | Decimal | ❌ | 他评分数 |
| managerComment | String | ❌ | 他评说明 |
| managerId | UUID | ❌ | 评估经理 |
| managerEvaluatedAt | DateTime | ❌ | 他评时间 |
| finalScore | Decimal | ❌ | 最终分数 |
| deletedAt | DateTime | ❌ | 软删除时间 |

#### Evaluation360（360 评估）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| cycleId | UUID | ✅ | 所属周期 |
| targetId | UUID | ✅ | 被评估者 ID |
| status | Enum | ✅ | 状态（DRAFT/IN_PROGRESS/COLLECTING/COMPLETED/CANCELLED） |
| templateId | UUID | ❌ | 评估问卷模板 |
| deadline | DateTime | ✅ | 截止日期 |
| minEvaluators | Int | ❌ | 最少评估人数 |
| createdBy | UUID | ✅ | 发起人 |
| createdAt | DateTime | ✅ | 创建时间 |
| updatedAt | DateTime | ✅ | 更新时间 |
| deletedAt | DateTime | ❌ | 软删除时间 |

#### EvaluationTask（360 评估任务）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| evaluationId | UUID | ✅ | 所属评估 |
| evaluatorId | UUID | ✅ | 评估者 ID |
| relationType | Enum | ✅ | 关系类型（SELF/SUPERVISOR/PEER/SUBORDINATE/CROSS_FUNCTIONAL） |
| status | Enum | ✅ | 状态（PENDING/SUBMITTED/EXPIRED/CANCELLED） |
| isAnonymous | Boolean | ✅ | 是否匿名 |
| submittedAt | DateTime | ❌ | 提交时间 |
| createdAt | DateTime | ✅ | 创建时间 |
| deletedAt | DateTime | ❌ | 软删除时间 |

#### EvaluationResponse（360 评估回答）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| taskId | UUID | ✅ | 关联任务 |
| dimension | String | ✅ | 评估维度 |
| score | Decimal | ✅ | 评分 |
| comment | String | ❌ | 文字反馈 |
| deletedAt | DateTime | ❌ | 软删除时间 |

#### GradeAdjustmentLog（等级调整日志）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| resultId | UUID | ✅ | 关联绩效结果 |
| previousGradeCode | String | ✅ | 调整前等级代码 |
| newGradeCode | String | ✅ | 调整后等级代码 |
| reason | String | ✅ | 调整原因 |
| adjustedBy | UUID | ✅ | 调整人 |
| adjustedAt | DateTime | ✅ | 调整时间 |
| deletedAt | DateTime | ❌ | 软删除时间 |

#### StrategicObjective（战略目标）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| cycleId | UUID | ✅ | 所属周期 |
| name | String | ✅ | 目标名称 |
| description | String | ❌ | 目标描述 |
| departmentId | UUID | ❌ | 所属部门 |
| seq | Int | ✅ | 排序序号 |
| createdBy | UUID | ✅ | 创建人 |
| createdAt | DateTime | ✅ | 创建时间 |
| updatedAt | DateTime | ✅ | 更新时间 |
| deletedAt | DateTime | ❌ | 软删除时间 |

> **注意**：战略目标的人员分配通过独立关联表 `StrategicObjectiveAssignment` 管理，不再使用 `assigneeIds: JSON` 字段。前端通过 `assignStrategicObjective(id, assigneeIds)` 独立接口进行分配。

#### StrategicObjectiveAssignment（战略目标分配）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| objectiveId | UUID | ✅ | 所属战略目标 |
| assigneeId | UUID | ✅ | 被分配的员工 ID |
| createdAt | DateTime | ✅ | 创建时间 |
| deletedAt | DateTime | ❌ | 软删除时间 |

#### Evaluation360Template（360 评估模板）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | UUID | ✅ | 主键 |
| name | String | ✅ | 模板名称 |
| description | String | ❌ | 模板描述 |
| dimensions | JSON | ✅ | 评估维度数组 |
| relationshipTypes | JSON | ✅ | 可用关系类型数组 |
| isDefault | Boolean | ✅ | 是否默认模板 |
| isActive | Boolean | ✅ | 是否启用 |
| organizationId | UUID | ❌ | 所属组织 ID（null = 平台级模板） |
| createdBy | UUID | ✅ | 创建人 |
| createdAt | DateTime | ✅ | 创建时间 |
| updatedAt | DateTime | ✅ | 更新时间 |
| deletedAt | DateTime | ❌ | 软删除时间 |

**dimensions JSON 结构**：

> **维度 id 规则**：每个维度必须有唯一 `id` 字段。创建模板时，后端自动为每个维度生成 `id`（格式 `dim-{index}`）。维度 `id` 一旦生成不可变更，前端反馈表单按 `dimensionId` 存储评分，维度重排不影响历史数据映射。

```json
[
  {
    "id": "dim-0",
    "name": "沟通能力",
    "nameEn": "Communication",
    "description": "清晰表达和有效倾听的能力",
    "descriptionEn": "Ability to communicate clearly and listen effectively"
  }
]
```

**relationshipTypes JSON 结构**：
```json
["SELF", "SUPERVISOR", "PEER", "SUBORDINATE", "CROSS_FUNCTIONAL"]
```

### 关系与约束

| 关系/约束 | 说明 |
|-----------|------|
| Cycle → GradeConfig | 多对一：周期引用等级配置 |
| Cycle → Cycle (parent) | 自引用：支持父子周期结构 |
| Cycle → PerformanceResult | 一对多：一个周期包含多个绩效结果 |
| Cycle → KpiAssignment | 一对多：一个周期包含多个 KPI 分配 |
| KpiAssignment → KpiAssessment | 一对一：每个分配对应一个考核记录 |
| Cycle → Evaluation360 | 一对多：一个周期可有多个 360 评估 |
| Evaluation360 → Evaluation360Template | 多对一：引用评估模板 |
| Evaluation360 → EvaluationTask | 一对多：一个评估包含多个任务 |
| EvaluationTask → EvaluationResponse | 一对多：一个任务包含多个回答 |
| User → EvaluationTask (evaluator) | 多对一：评估者 |
| PerformanceResult → GradeAdjustmentLog | 一对多：一个绩效结果可有多次等级调整 |
| Cycle → StrategicObjective | 一对多：一个周期可有多个战略目标 |
| StrategicObjective → StrategicObjectiveAssignment | 一对多：一个目标可分配给多个员工 |
| StrategicObjectiveAssignment → User | 多对一：分配关联用户 |
| KpiAssignment → KpiAssignment (parent) | 自引用：支持 KPI 层级结构 |
| KpiAssignment → KpiDependency (outgoing) | 一对多：一个 KPI 分配可发起多个跨部门依赖 |
| KpiAssignment → KpiDependency (incoming) | 一对多：一个 KPI 分配可被多个依赖指向 |
| PerformanceCycle → Organization | 多对一：周期归属组织（强约束） |
| PerformanceResult → Organization | 多对一：冗余组织 ID，简化分析查询 |
| GradeConfig → Organization | 多对一：等级配置归属组织（可选，null = 平台级） |
| Evaluation360Template → Organization | 多对一：模板归属组织（可选，null = 平台级） |
| KPI 名称唯一约束 | 逻辑约束：同一员工同一周期不允许同名 KPI（排除软删除）。Prisma 表达：@@unique([cycleId, employeeId, name])（仅表达字段结构，不支持 partial unique）。数据库实现：通过迁移原生 SQL 创建 `CREATE UNIQUE INDEX ... WHERE deleted_at IS NULL`。 |
| weight 约束 | 同一员工同一周期下所有 KpiAssignment 的 weight 总和必须为 100 |
| 状态约束 | 周期 ARCHIVED 后，关联数据不可修改 |
| 结果约束 | 每个员工每周期仅一条绩效结果记录（cycleId + employeeId 唯一） |
| 等级配置约束 | grades 数组中分数区间不能重叠，且必须覆盖 0-100 |
| 360 模板约束 | 默认模板按组织范围唯一：organizationId 相同的模板中最多一个 isDefault = true；organizationId = NULL 的平台级模板单独管理。Prisma `@@index([isDefault, isActive])` 仅为查询优化，唯一性需通过迁移原生 SQL 创建 partial unique index 或应用层校验保证。 |
| 等级配置默认约束 | 默认等级配置按组织范围唯一：organizationId 相同的配置中最多一个 isDefault = true；organizationId = NULL 的平台级配置单独管理。实现方式同 360 模板约束。 |

---

### ER 图

```mermaid
erDiagram
    GradeConfig ||--o{ PerformanceCycle : configures
    PerformanceCycle ||--o{ PerformanceCycle : "parent/child"
    PerformanceCycle ||--o{ PerformanceResult : contains
    PerformanceCycle ||--o{ KpiAssignment : contains
    PerformanceCycle ||--o{ Evaluation360 : contains
    PerformanceCycle ||--o{ StrategicObjective : contains

    KpiAssignment ||--o{ KpiAssignment : "parent/child"
    KpiAssignment ||--|| KpiAssessment : assessed_by
    KpiAssignment ||--o{ KpiDependency : "outgoing"
    KpiAssignment ||--o{ KpiDependency : "incoming"

    Evaluation360 ||--o{ EvaluationTask : distributes
    Evaluation360 }o--|| Evaluation360Template : uses
    EvaluationTask ||--o{ EvaluationResponse : contains

    PerformanceResult ||--o{ GradeAdjustmentLog : tracks

    StrategicObjective ||--o{ StrategicObjectiveAssignment : assigns

```

### Prisma Schema 设计

```prisma
// platform_performance.prisma
// 简化版：省略 @db.Uuid / @db.Timestamptz / @map 等数据库映射注解，仅展示逻辑结构

enum CycleType {
  MONTHLY
  QUARTERLY
  SEMI_ANNUAL
  ANNUAL
  CUSTOM
}

enum CycleStatus {
  DRAFT
  GOAL_SETTING
  IN_PROGRESS
  EVALUATING
  CALIBRATING
  CONFIRMING        // 结果确认中（员工确认/申诉）
  COMPLETED
  ARCHIVED
}

// 注意：绩效等级已改为可配置，不再使用固定枚举
// 等级代码存储为 String，引用 GradeConfig.grades[].code

enum KpiAssignmentStatus {
  DRAFT              // 草稿
  SUBMITTED          // 已提交
  APPROVED           // 已批准
  REJECTED           // 已拒绝
}

enum KpiDependencyStatus {
  PENDING
  CONFIRMED
  REJECTED
}

enum ResultConfirmStatus {
  PENDING            // 待确认
  CONFIRMED          // 已确认
  APPEALED           // 已申诉
  APPEAL_RESOLVED    // 申诉已处理
}

enum KpiAssessmentStatus {
  PENDING
  SELF_EVALUATED
  MANAGER_EVALUATED
  CONFIRMED
}

enum Evaluation360Status {
  DRAFT
  IN_PROGRESS
  COLLECTING
  COMPLETED
  CANCELLED
}

enum EvaluationTaskStatus {
  PENDING
  SUBMITTED
  EXPIRED
  CANCELLED
}

enum RelationType {
  SELF
  SUPERVISOR
  PEER
  SUBORDINATE
  CROSS_FUNCTIONAL   // 跨部门
}

model GradeConfig {
  id             String   @id @default(uuid())
  name           String                     // 配置名称
  description    String?
  grades         Json                       // 等级定义数组
  isDefault      Boolean  @default(false)
  isActive       Boolean  @default(true)
  organizationId String?                    // 所属组织（null = 平台级配置）
  createdBy      String
  createdAt      DateTime @default(now())
  updatedAt      DateTime @updatedAt
  deletedAt      DateTime?

  organization Organization? @relation("GradeConfigOrganization", fields: [organizationId], references: [id])
  cycles       PerformanceCycle[]

  @@index([isDefault, isActive])
  @@index([organizationId])
  @@map("grade_config")
}

model PerformanceCycle {
  id                  String       @id @default(uuid())
  name                String
  type                CycleType
  status              CycleStatus  @default(DRAFT)
  startDate           DateTime
  endDate             DateTime
  gradeConfigId       String?                  // 绩效等级配置
  parentCycleId       String?                  // 父周期 ID（支持子周期结构）
  resultsPublishedAt  DateTime?                // 结果发布时间
  resultsPublishedBy  String?                  // 结果发布人
  organizationId      String                   // 所属组织（强约束）
  createdBy           String
  createdAt           DateTime     @default(now())
  updatedAt           DateTime     @updatedAt
  deletedAt           DateTime?

  organization        Organization        @relation("PerformanceCycleOrganization", fields: [organizationId], references: [id])
  gradeConfig         GradeConfig?        @relation(fields: [gradeConfigId], references: [id])
  parentCycle         PerformanceCycle?    @relation("CycleHierarchy", fields: [parentCycleId], references: [id])
  childCycles         PerformanceCycle[]   @relation("CycleHierarchy")
  kpiAssignments      KpiAssignment[]
  evaluations         Evaluation360[]
  results             PerformanceResult[]
  strategicObjectives StrategicObjective[]

  @@index([status])
  @@index([startDate, endDate])
  @@index([type, status])
  @@index([organizationId])
  @@index([parentCycleId])
  @@index([gradeConfigId])
  @@map("performance_cycle")
}

model PerformanceResult {
  id                    String              @id @default(uuid())
  cycleId               String
  employeeId            String
  kpiScore              Decimal?                  // KPI 综合得分
  kpiWeight             Decimal?                  // KPI 权重
  e360Score             Decimal?                  // 360 评估得分
  e360Weight            Decimal?                  // 360 权重
  totalScore            Decimal?                  // 综合总分，结果生成时填充
  proposedGradeCode     String?                   // 汇总分数匹配的原始等级，结果生成时填充
  proposedGradeName     String?
  gradeCode             String?                   // 最终等级代码（校准后可能不同），结果生成时填充
  gradeName             String?                   // 最终等级名称
  isPublished           Boolean             @default(false)
  publishedAt           DateTime?
  viewedByEmployee      Boolean             @default(false)
  viewedAt              DateTime?
  confirmStatus         ResultConfirmStatus @default(PENDING)
  appealReason          String?                   // 申诉原因
  appealResponse        String?                   // HR 申诉回复
  appealResolvedAt      DateTime?
  selfOverallComment    String?                   // 员工整体自评
  managerOverallComment String?                   // 经理整体评语
  remarks               String?                   // HR 备注
  organizationId        String                    // 冗余组织 ID（简化分析查询）
  createdAt             DateTime            @default(now())
  updatedAt             DateTime            @updatedAt
  deletedAt             DateTime?

  cycle          PerformanceCycle    @relation(fields: [cycleId], references: [id])
  organization   Organization       @relation("PerformanceResultOrganization", fields: [organizationId], references: [id])
  adjustmentLogs GradeAdjustmentLog[]

  @@unique([cycleId, employeeId])
  @@index([employeeId, isPublished])
  @@index([cycleId, confirmStatus])
  @@index([cycleId, gradeCode])
  @@index([organizationId])
  @@map("performance_result")
}

model KpiAssignment {
  id            String              @id @default(uuid())
  cycleId       String
  employeeId    String
  name          String                    // KPI 名称（行内创建，必填）
  description   String?                   // KPI 描述
  weight        Decimal
  unit          String?                   // 单位
  baseTarget    String?                   // 基本目标
  stretchTarget String?                   // 挑战目标
  targetValue   Decimal?
  parentId      String?                   // 父 KPI ID（支持层级结构）
  seq           Int?                      // 排序序号
  status        KpiAssignmentStatus @default(DRAFT)
  maturityScore Decimal?                  // 成熟度评分
  createdAt     DateTime            @default(now())
  updatedAt     DateTime            @updatedAt
  deletedAt     DateTime?

  cycle                PerformanceCycle @relation(fields: [cycleId], references: [id])
  parent               KpiAssignment?   @relation("KpiHierarchy", fields: [parentId], references: [id])
  children             KpiAssignment[]  @relation("KpiHierarchy")
  assessment           KpiAssessment?
  outgoingDependencies KpiDependency[]  @relation("DependencySource")
  incomingDependencies KpiDependency[]  @relation("DependencyTarget")

  @@unique([cycleId, employeeId, name])
  @@index([cycleId, employeeId])
  @@index([cycleId, employeeId, status])
  @@index([cycleId, status])
  @@index([parentId])
  @@map("kpi_assignment")
}

model KpiDependency {
  id                 String              @id @default(uuid())
  sourceAssignmentId String
  targetAssignmentId String?
  targetUserId       String
  description        String?
  status             KpiDependencyStatus @default(PENDING)
  rejectionReason    String?
  confirmedAt        DateTime?
  createdAt          DateTime            @default(now())
  updatedAt          DateTime            @updatedAt
  deletedAt          DateTime?

  sourceAssignment KpiAssignment  @relation("DependencySource", fields: [sourceAssignmentId], references: [id], onDelete: Cascade)
  targetAssignment KpiAssignment? @relation("DependencyTarget", fields: [targetAssignmentId], references: [id])

  @@unique([sourceAssignmentId, targetUserId])
  @@index([targetUserId, status])
  @@index([sourceAssignmentId])
  @@map("kpi_dependency")
}

model KpiAssessment {
  id                 String              @id @default(uuid())
  assignmentId       String              @unique
  status             KpiAssessmentStatus @default(PENDING)
  selfScore          Decimal?
  completionNote     String?                   // 完成情况说明
  selfComment        String?
  selfEvaluatedAt    DateTime?
  managerScore       Decimal?
  managerComment     String?
  managerId          String?
  managerEvaluatedAt DateTime?
  finalScore         Decimal?
  deletedAt          DateTime?

  assignment KpiAssignment @relation(fields: [assignmentId], references: [id], onDelete: Cascade)

  @@index([status])
  @@map("kpi_assessment")
}

model Evaluation360 {
  id            String              @id @default(uuid())
  cycleId       String
  targetId      String
  status        Evaluation360Status @default(DRAFT)
  templateId    String?
  deadline      DateTime
  minEvaluators Int?
  createdBy     String
  createdAt     DateTime            @default(now())
  updatedAt     DateTime            @updatedAt
  deletedAt     DateTime?

  cycle    PerformanceCycle       @relation(fields: [cycleId], references: [id])
  template Evaluation360Template? @relation(fields: [templateId], references: [id])
  tasks    EvaluationTask[]

  @@index([cycleId, targetId])
  @@index([cycleId, targetId, status])
  @@index([status])
  @@index([templateId])
  @@map("evaluation_360")
}

model EvaluationTask {
  id           String               @id @default(uuid())
  evaluationId String
  evaluatorId  String
  relationType RelationType
  status       EvaluationTaskStatus @default(PENDING)
  isAnonymous  Boolean              @default(true)
  submittedAt  DateTime?
  createdAt    DateTime             @default(now())
  deletedAt    DateTime?

  evaluation Evaluation360        @relation(fields: [evaluationId], references: [id], onDelete: Cascade)
  responses  EvaluationResponse[]

  @@unique([evaluationId, evaluatorId])
  @@index([evaluatorId, status])
  @@map("evaluation_task")
}

model EvaluationResponse {
  id        String    @id @default(uuid())
  taskId    String
  dimension String
  score     Decimal
  comment   String?
  deletedAt DateTime?

  task EvaluationTask @relation(fields: [taskId], references: [id], onDelete: Cascade)

  @@index([taskId])
  @@map("evaluation_response")
}

model GradeAdjustmentLog {
  id                String   @id @default(uuid())
  resultId          String
  previousGradeCode String                    // 调整前等级代码
  newGradeCode      String                    // 调整后等级代码
  reason            String
  adjustedBy        String
  adjustedAt        DateTime @default(now())
  deletedAt         DateTime?

  result PerformanceResult @relation(fields: [resultId], references: [id], onDelete: Cascade)

  @@index([resultId])
  @@map("grade_adjustment_log")
}

model StrategicObjective {
  id           String   @id @default(uuid())
  cycleId      String
  seq          Int      @default(0)        // 排序序号
  name         String
  description  String?
  departmentId String?
  createdBy    String
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt
  deletedAt    DateTime?

  cycle       PerformanceCycle              @relation(fields: [cycleId], references: [id])
  assignments StrategicObjectiveAssignment[]

  @@index([cycleId])
  @@index([departmentId])
  @@map("strategic_objective")
}

model StrategicObjectiveAssignment {
  id          String   @id @default(uuid())
  objectiveId String
  assigneeId  String
  createdAt   DateTime @default(now())
  deletedAt   DateTime?

  objective StrategicObjective @relation(fields: [objectiveId], references: [id], onDelete: Cascade)
  assignee  User               @relation("StrategicObjectiveAssignee", fields: [assigneeId], references: [id])

  @@unique([objectiveId, assigneeId])
  @@index([assigneeId])
  @@map("strategic_objective_assignment")
}

model Evaluation360Template {
  id                String   @id @default(uuid())
  name              String
  description       String?
  dimensions        Json                   // 评估维度数组 [{name, nameEn, description, descriptionEn}]
  relationshipTypes Json                   // 可用关系类型 ["SELF","SUPERVISOR",...]
  isDefault         Boolean  @default(false)
  isActive          Boolean  @default(true)
  organizationId    String?                // 所属组织（null = 平台级模板）
  createdBy         String
  createdAt         DateTime @default(now())
  updatedAt         DateTime @updatedAt
  deletedAt         DateTime?

  organization Organization? @relation("Evaluation360TemplateOrganization", fields: [organizationId], references: [id])
  evaluations  Evaluation360[]

  @@index([isDefault, isActive])
  @@index([organizationId])
  @@map("evaluation_360_template")
}

```

### 索引设计

| 表名 | 索引 | 类型 | 说明 |
|------|------|------|------|
| grade_config | (isDefault, isActive) | 复合索引 | 查询默认/活跃配置 |
| grade_config | (organizationId) | 索引 | 按组织查询配置 |
| performance_cycle | (status) | 索引 | 按状态筛选周期 |
| performance_cycle | (startDate, endDate) | 复合索引 | 按日期范围查询 |
| performance_cycle | (type, status) | 复合索引 | 按类型和状态查询 |
| performance_cycle | (organizationId) | 索引 | 按组织查询周期 |
| performance_cycle | (parentCycleId) | 索引 | 查询子周期 |
| performance_cycle | (gradeConfigId) | 索引 | 按等级配置查询 |
| performance_result | (cycleId, employeeId) | 唯一索引 | 每员工每周期一条结果 |
| performance_result | (employeeId, isPublished) | 复合索引 | 员工查询已发布结果 |
| performance_result | (cycleId, confirmStatus) | 复合索引 | 按确认状态查询结果 |
| performance_result | (cycleId, gradeCode) | 复合索引 | 按等级代码查询结果 |
| performance_result | (organizationId) | 索引 | 按组织查询结果 |
| kpi_assignment | (cycleId, employeeId) | 复合索引 | 按周期和员工查询 |
| kpi_assignment | (cycleId, employeeId, name) | 唯一索引 | 同一员工同一周期不允许同名 KPI |
| kpi_assignment | (cycleId, employeeId, status) | 复合索引 | 按状态查询分配 |
| kpi_assignment | (cycleId, status) | 复合索引 | 按周期和状态查询 |
| kpi_assignment | (parentId) | 索引 | 查询子 KPI |
| kpi_dependency | (sourceAssignmentId, targetUserId) | 唯一索引 | 同一 KPI 对同一用户不重复依赖 |
| kpi_dependency | (targetUserId, status) | 复合索引 | 查询用户收到的待确认依赖 |
| kpi_dependency | (sourceAssignmentId) | 索引 | 按源 KPI 查询依赖 |
| kpi_assessment | (status) | 索引 | 按状态查询考核记录 |
| evaluation_360 | (cycleId, targetId) | 复合索引 | 按周期和目标查询 |
| evaluation_360 | (cycleId, targetId, status) | 复合索引 | 按周期、目标和状态查询 |
| evaluation_360 | (status) | 索引 | 按状态查询评估 |
| evaluation_360 | (templateId) | 索引 | 按模板查询评估 |
| evaluation_task | (evaluatorId, status) | 复合索引 | 查询用户待完成任务 |
| evaluation_task | (evaluationId, evaluatorId) | 唯一索引 | 同一评估中每个评估者唯一 |
| evaluation_response | (taskId) | 索引 | 按任务查询回答 |
| grade_adjustment_log | (resultId) | 索引 | 按绩效结果查询调整历史 |
| strategic_objective | (cycleId) | 索引 | 按周期查询战略目标 |
| strategic_objective | (departmentId) | 索引 | 按部门查询战略目标 |
| strategic_objective_assignment | (objectiveId, assigneeId) | 唯一索引 | 同一目标对同一员工不重复分配 |
| strategic_objective_assignment | (assigneeId) | 索引 | 按员工查询分配 |
| evaluation_360_template | (isDefault, isActive) | 复合索引 | 查询默认/活跃模板 |
| evaluation_360_template | (organizationId) | 索引 | 按组织查询模板 |

### 数据迁移注意事项

1. **软删除策略**：核心主表使用软删除（deletedAt 字段），关联/日志表按需选择软删除或硬删除
2. **审计日志**：关键操作自动记录到审计表
3. **历史数据**：周期归档后数据不可修改，仅可查询

---

**创建时间**: 2025-12-25
**更新时间**: 2026-03-20
**版本**: v5.0
