# 用户与组织架构管理 - 用户场景文档

> **版本**: v2.4  
> **最后更新**: 2026-05-19  
> **编写者**: FFOA 产品团队

> **v2.4 变更**：场景 9「用户登录」下新增三个子场景——9a Entra SSO 登录、9b 首次 SSO JIT 建账号、9c Entra 不可用时回退密码登录（issue #334）。

---

## 📋 文档说明

本文档描述用户与组织架构管理模块的用户使用场景，帮助团队理解用户如何使用系统完成任务。

---

## 👥 用户角色

| 角色 | 描述 | 权限级别 |
|------|------|---------|
| **系统管理员** | 负责系统整体配置、用户和组织架构管理 | 管理员（全局权限） |
| **HR 管理员** | 人力资源部门管理员，负责员工信息维护 | 管理员（全局权限） |
| **普通员工** | 系统普通用户 | 普通用户（个人权限） |

---

## 🎯 场景 1: 新员工入职

### 场景描述

**用户角色**: HR 管理员

**使用时机**: 公司有新员工入职时

**目标**: 快速为新员工创建账号，分配部门和权限，使其能够登录系统开始工作

### 前置条件

- [x] HR 管理员已登录系统
- [x] HR 管理员拥有 `user:create` 权限
- [x] 已获得新员工的基本信息（姓名、邮箱、员工编号等）
- [x] 已确定新员工的部门归属和岗位

### 正常路径

| 步骤 | 用户操作 | 系统响应 | 页面/状态 |
|------|---------|---------|----------|
| 1 | 导航到用户管理页面 | 显示用户列表 | 用户管理页 |
| 2 | 点击「新建用户」按钮 | 打开创建用户表单 | 创建用户对话框 |
| 2.1 | 确认创建类型 | 默认本地用户 | 表单页面 |
| 3 | 填写用户基本信息 | 实时验证字段 | 表单页面 |
| 4 | 选择组织和部门 | 加载组织树和部门树 | 表单页面 |
| 5 | 选择岗位和设置上级 | 显示该部门的可选上级 | 表单页面 |
| 6 | 分配初始角色 | 预览权限列表 | 表单页面 |
| 7 | 点击「确认创建」 | 创建用户并发送欢迎邮件 | 用户列表页 |

**详细说明**：

**步骤1**: 导航到用户管理
- HR 管理员点击侧边栏「组织管理」→「用户管理」
- 系统显示用户列表，包含搜索和筛选功能

**步骤2-2.1**: 创建类型说明（v2.1.25）
- 手工创建仅支持 **本地用户**（LOCAL）
- AD 用户**只能通过 Entra ID 同步创建**，不支持手工创建

**步骤3**: 填写基本信息
- 用户名（必填）：唯一标识符，不可重复
- 显示名称（必填）：员工姓名
- 邮箱（必填）：用于登录和接收通知
- 电话（选填）：联系方式
- 员工编号（选填）：HR 系统编号
- **身份源说明**：
  - 本地用户：系统自动生成初始密码，通过邮件发送，仅用于测试
  - AD 用户：**不支持手动创建**，必须通过 Entra ID 同步

**步骤4**: 设置组织关系（v2.0 架构）
- **选择组织**（必填）：用户归属的法人实体（如 FF China）
- **选择主部门**（必填）：从该组织的部门树中选择
- 部门自动继承组织的区域信息

**步骤5**: 配置岗位和汇报
- 选择岗位：从岗位列表中选择
- 设置直属上级：只能选择同部门的成员

**步骤6**: 分配权限（v2.1 组织级隔离）
- 默认分配 Employee 角色
- **组织级角色分配**：
  - 可为用户在指定组织分配角色
  - 示例：在 FF China 分配 "HR管理员" 角色
  - 支持同一用户在不同组织有不同角色
- 系统显示角色包含的权限预览

**步骤7**: 完成创建
- 系统创建用户记录：
  - status = ACTIVE
  - source = LOCAL（**注意**：AD 用户不支持手动创建）
  - organizationId = 选择的组织
- 创建 UserDepartment 记录（部门归属）
- 创建 UserRole 记录（角色分配，包含 organizationId）
- **本地用户**：发送欢迎邮件，包含临时密码
- 记录审计日志
- 返回用户列表页，显示成功提示

### 异常路径

#### 异常 1.1: 用户名或邮箱已存在

**触发条件**: 输入的用户名或邮箱已被其他用户使用

| 步骤 | 用户操作 | 系统响应 | 错误处理 |
|------|---------|---------|---------|
| 3 | 填写已存在的用户名/邮箱 | 显示验证错误 | 提示"用户名已被使用，请使用其他用户名" |

**恢复步骤**: 
1. 修改用户名或邮箱
2. 如是软删除用户，系统提供恢复选项
3. 重新提交

---

#### 异常 1.2: 上级不在所选部门

**触发条件**: 选择的直属上级不是该部门的成员

**系统行为**: 阻止提交，显示错误提示

**用户反馈**: "上级 'XXX' 不在部门 'YYY' 中，请选择该部门内的其他上级"

**恢复步骤**:
1. 选择该部门内的其他上级
2. 或为上级添加到该部门（创建多部门归属）

---

#### 异常 1.3: 邮件发送失败

**触发条件**: SMTP 服务不可用或邮箱地址无效

**系统行为**: 
- 用户创建成功
- 显示警告："用户创建成功，但欢迎邮件发送失败"
- 记录错误日志

**用户反馈**: 提示 HR 管理员需要手动通知员工账号信息

---

### 后置条件

- ✅ 本地用户账号已创建（status = ACTIVE, source = LOCAL）
- ✅ 用户已分配到指定部门（UserDepartment 记录已创建）
- ✅ 用户已分配初始角色（UserRole 记录已创建）
- ✅ 欢迎邮件已发送（或记录发送失败日志）
- ✅ 审计日志已记录创建操作
- ⚠️ **注意**：AD 用户不支持手动创建，必须通过 Entra ID 同步

### 业务规则

1. **唯一性约束**: 用户名、邮箱、员工编号必须全局唯一
2. **初始状态**: 新创建用户默认状态为 ACTIVE
3. **来源标识**: 本地创建用户 source = LOCAL
4. **AD 用户创建限制** ⭐：AD 用户**只能通过 Entra ID 同步创建**，不支持手动创建
5. **汇报关系约束**: 直属上级必须是同部门成员
6. **主部门**: 首次创建时添加的部门自动设为主部门（isPrimary = true）

---

## 🎯 场景 2: 配置多部门归属

### 场景描述

**用户角色**: HR 管理员

**使用时机**: 员工需要兼职多个部门，或组织采用矩阵式管理时

**目标**: 为用户添加多个部门归属，每个归属设置独立的岗位和汇报关系

### 前置条件

- [x] 用户账号已存在
- [x] HR 管理员拥有 `user:update` 权限
- [x] 已确认用户需要归属的所有部门
- [x] 已确认每个部门的岗位和直属上级

### 正常路径

| 步骤 | 用户操作 | 系统响应 | 页面/状态 |
|------|---------|---------|----------|
| 1 | 在用户列表中找到目标用户 | 显示搜索结果 | 用户列表页 |
| 2 | 点击用户名称进入详情页 | 显示用户详情 | 用户详情页 |
| 3 | 切换到「部门归属」标签页 | 显示当前所有部门归属 | 部门归属页 |
| 4 | 点击「添加部门归属」 | 打开添加表单 | 添加归属对话框 |
| 5 | 选择新部门和岗位 | 加载该部门的可选上级 | 表单页面 |
| 6 | 设置直属上级 | 验证上级在该部门内 | 表单页面 |
| 7 | 选择是否设为主部门 | 如选是，标记其他部门为非主 | 表单页面 |
| 8 | 点击「保存」 | 创建新的部门归属记录 | 部门归属页 |

**详细说明**：

**步骤1-3**: 进入用户详情
- 支持按姓名、邮箱、员工编号搜索
- 用户详情页显示 4 个标签：基本信息、部门归属、角色权限、操作日志
- 部门归属页显示：主部门（标记"主"）、所有兼职部门

**步骤4-6**: 添加新归属
- 选择部门：从部门树中选择（可跨组织）
- 选择岗位：该部门适用的岗位
- 选择上级：只显示该部门的成员
- 设置职位头衔（选填）：如"技术顾问"

**步骤7**: 主部门设置
- 如设为主部门，系统自动将其他部门的 isPrimary 改为 false
- 每个用户必须有且只有一个主部门

**步骤8**: 保存和验证
- 系统创建 UserDepartment 记录
- 验证汇报关系有效性
- 记录审计日志

### 异常路径

#### 异常 2.1: 该部门已存在归属

**触发条件**: 用户在该部门已有归属记录

**系统行为**: 阻止添加，提示错误

**用户反馈**: "用户已归属部门 'XXX'，同一用户在同一部门只能有一个归属"

**恢复步骤**: 编辑现有归属或选择其他部门

---

#### 异常 2.2: 尝试删除唯一的主部门

**触发条件**: 用户只有一个部门归属（主部门），尝试删除

**系统行为**: 阻止删除

**用户反馈**: "不能删除用户的唯一部门归属，如需删除请先添加其他部门或删除用户"

---

### 后置条件

- ✅ 新的 UserDepartment 记录已创建
- ✅ 用户可以同时属于多个部门
- ✅ 每个归属有独立的岗位（positionId）和上级（managerId）
- ✅ 只有一个主部门（isPrimary = true）
- ✅ 审计日志已记录变更

### 业务规则

1. **唯一性约束**: 同一用户在同一部门只能有一个归属
2. **主部门唯一**: 必须有且只有一个主部门
3. **汇报关系约束**: 每个归属的上级必须是该部门成员
4. **删除顺序**: 删除主部门前必须先设置其他主部门
5. **审批默认**: 审批流程默认使用主部门的汇报关系

---

## 🎯 场景 3: Microsoft Entra ID 用户同步

### 场景描述

**用户角色**: IT 管理员 / 系统管理员

**使用时机**: 定期同步企业目录中的用户信息到系统

**目标**: 从 Microsoft Entra ID 同步用户基本信息，保持用户数据与企业目录一致

> **重要说明** (v2.1.25)：
> - Entra ID 仅用于**同步用户信息**（显示名、邮箱、员工ID等），不用于身份认证
> - 同步的用户统一使用 **LDAP/AD 认证**进行登录
> - **这是创建 AD 用户的唯一方式**，不支持手动创建 AD 用户

### 前置条件

- [x] 已配置 Microsoft Entra ID 连接（Client ID、Tenant ID、Client Secret）
- [x] 用户拥有 `organization:sync` 权限
- [x] Entra ID 应用已授予必要的 API 权限
- [x] 网络可访问 Microsoft Graph API

### 正常路径

| 步骤 | 用户操作 | 系统响应 | 页面/状态 |
|------|---------|---------|----------|
| 1 | 导航到外部集成配置页 | 显示 Entra ID 配置 | 外部集成页 |
| 2 | 点击「测试连接」 | 验证配置并显示可同步用户数 | 配置页 |
| 3 | 查看同步规则配置 | 显示字段映射和默认设置 | 配置页 |
| 4 | 点击「立即同步」 | 开始同步进度 | 同步进度页 |
| 5 | 等待同步完成 | 显示实时进度 | 同步进度页 |
| 6 | 查看同步结果 | 显示统计和详细日志 | 同步结果页 |

**详细说明**：

**步骤1-2**: 测试连接
- 系统调用 Microsoft Graph API 测试连接
- 验证 Client Secret 是否有效
- 查询可同步的用户数量
- 显示连接状态（成功/失败）

**步骤3**: 同步规则
- 字段映射：
  - `userPrincipalName` → `email`
  - `displayName` → `displayName`
  - `employeeId` → `employeeId`
- 同步范围：全部用户或特定组
- 冲突策略：HR 字段覆盖，本地字段保留

**步骤4-5**: 执行同步（v2.1.24 已支持定时自动同步）
- 同步规则：
  - 新用户：创建账号（source = LDAP，⚠️ 不是 ENTRA）
  - 已存在用户（匹配 employeeId 或 email）：更新信息
  - Entra 中禁用用户：本地状态改为 SUSPENDED
  - Entra 中已删除用户：跳过（不自动删除本地用户）
  - TERMINATED 用户：不自动复活
- 显示实时进度：已同步 X / 总共 Y
- ⭐ **定时自动同步**（v2.1.24）：支持配置自动同步周期和时间

**步骤6**: 查看结果
- 统计数据：
  - 新增用户数
  - 更新用户数
  - 跳过用户数
  - 错误数
- 详细日志：每个用户的处理结果
- 下载同步报告（CSV 格式）

### 异常路径

#### 异常 3.1: 连接失败

**触发条件**: 网络不可达或 Entra ID 服务不可用

**系统行为**: 中止同步，显示错误信息

**用户反馈**: "无法连接到 Microsoft Graph API，请检查网络连接"

**恢复步骤**: 检查网络后重试

---

#### 异常 3.2: 认证失败

**触发条件**: Client Secret 过期或无效

**系统行为**: 中止同步

**用户反馈**: "认证失败，请检查 Client Secret 是否正确或已过期"

**恢复步骤**: 更新 Client Secret 后重试

---

#### 异常 3.3: 部分用户同步失败

**触发条件**: 某些用户数据格式不正确或字段映射失败

**系统行为**: 
- 跳过失败的用户
- 继续同步其他用户
- 记录失败详情

**用户反馈**: "同步完成，部分用户失败（X 个），请查看详细日志"

**恢复步骤**: 查看失败日志，手动修正后重新同步

---

### 后置条件

- ✅ Entra ID 用户成功同步到系统
- ✅ 用户 `source` 字段标记为 **LDAP**（v2.1.25 更正）
- ✅ 用户 `externalId` 存储 Entra Object ID
- ✅ 用户 `ldapSyncedAt` 记录同步时间
- ✅ 同步日志完整记录所有操作
- ✅ 审计日志记录同步操作
- ✅ 定时同步任务已配置（如启用）

### 业务规则

1. **同步范围**: 只同步基本信息（姓名、邮箱、员工ID），不同步部门和权限
2. **冲突策略**: HR 字段（displayName）以 Entra 为准，本地字段（工号、手机）保留
3. **状态映射**: Entra 禁用用户 → 本地 SUSPENDED，启用用户 → 本地 ACTIVE
4. **TERMINATED 保护**: 本地已标记 TERMINATED 的用户不会自动复活
5. **权限分配**: 同步的用户不会自动分配任何权限
6. **并发控制**: 同一时间只允许一个同步任务运行
7. **身份源标记** ⭐：同步的用户 source = **LDAP**（不是 ENTRA），统一使用 LDAP/AD 认证
8. **定时同步** (v2.1.24)：支持配置自动同步周期（如每天、每周）和同步时间

---

## 🎯 场景 4: 组织架构调整

### 场景描述

**用户角色**: 系统管理员 / HR 管理员

**使用时机**: 公司组织架构发生变化，需要调整部门结构

**目标**: 创建新部门、调整部门层级、转移员工归属

### 前置条件

- [x] 用户拥有 `department:update` 权限
- [x] 已确认新的组织架构方案
- [x] 已评估调整的影响范围

### 正常路径

| 步骤 | 用户操作 | 系统响应 | 页面/状态 |
|------|---------|---------|----------|
| 1 | 进入组织架构管理页 | 显示组织架构树 | 组织架构页 |
| 2 | 点击「创建部门」 | 打开创建表单 | 创建部门对话框 |
| 3 | 填写部门信息 | 实时验证 | 表单页面 |
| 4 | 选择所属组织和父部门 | 显示组织树 | 表单页面 |
| 5 | 设置部门负责人 | 加载可选人员 | 表单页面 |
| 6 | 点击「保存」 | 创建部门并更新树 | 组织架构页 |
| 7 | 调整部门层级（如需要） | 拖拽部门到新位置 | 组织架构页 |
| 8 | 转移员工（如需要） | 批量更新员工归属 | 组织架构页 |

**详细说明**：

**步骤1-2**: 进入组织架构
- 显示树形结构，支持折叠/展开
- 显示每个部门的成员数和负责人
- 支持搜索部门

**步骤3-5**: 创建新部门
- 部门代码：同一组织内唯一
- 部门名称：如"技术部"
- 所属组织：必选（v2.0 架构）
- 父部门：null 表示顶级部门
- 部门负责人：可选

**步骤6**: 保存部门
- 系统验证部门代码唯一性
- 创建 Department 记录
- 自动更新组织架构树

**步骤7**: 调整层级（可选）
- 拖拽部门到新的父部门下
- 系统自动验证循环引用
- 更新 parentId

**步骤8**: 转移员工（可选）
- 批量选择需要转移的员工
- 选择目标部门
- 可选择保留原归属（多部门）或替换
- 重新设置岗位和汇报关系

### 异常路径

#### 异常 4.1: 部门代码重复

**触发条件**: 同一组织内已存在相同部门代码

**系统行为**: 阻止创建

**用户反馈**: "部门代码 'XXX' 在组织 'YYY' 中已存在"

---

#### 异常 4.2: 尝试删除有成员的部门

**触发条件**: 部门下还有员工归属

**系统行为**: 阻止删除

**用户反馈**: "无法删除部门 'XXX'，该部门还有 5 名员工，请先转移员工"

---

#### 异常 4.3: 尝试删除有子部门的部门

**触发条件**: 部门下还有子部门

**系统行为**: 阻止删除

**用户反馈**: "无法删除部门 'XXX'，该部门还有 3 个子部门"

---

### 后置条件

- ✅ 部门已创建或层级已调整
- ✅ 组织架构树已更新
- ✅ 员工归属已转移（如执行）
- ✅ 汇报关系已更新（如执行）
- ✅ 审计日志完整记录所有变更

### 业务规则

1. **组织归属**: 所有部门必须归属一个组织（v2.0）
2. **部门代码**: 同一组织内部门代码必须唯一
3. **层级约束**: 不允许循环引用（A → B → A）
4. **删除保护**: 有成员或子部门的部门不能删除
5. **批量操作**: 大规模调整建议分批进行

---

## 🎯 场景 5: 权限申请与分配

### 场景描述

**用户角色**: 普通员工（申请）、HR 管理员（审批）、系统管理员（分配）

**使用时机**: 员工需要额外权限来完成工作

**目标**: 通过审批流程为用户分配合适的角色和权限

### 前置条件

- [x] 用户账号已存在
- [x] 已明确需要的权限类型
- [x] 审批流程已配置

### 正常路径

| 步骤 | 用户操作 | 系统响应 | 页面/状态 |
|------|---------|---------|----------|
| 1 | 员工提交权限申请 | 创建审批流程 | 申请页面 |
| 2 | HR 管理员审批 | 更新流程状态 | 审批页面 |
| 3 | 系统管理员最终审批 | 更新流程状态 | 审批页面 |
| 4 | 系统管理员分配角色 | 创建 UserRole 记录 | 用户管理页 |
| 5 | 系统发送通知 | 邮件/站内信通知员工 | - |
| 6 | 员工验证权限 | 测试新权限 | 相关功能页 |

**详细说明**：

**步骤1**: 提交申请
- 员工描述需要的权限和理由
- 选择需要权限的组织（v2.1）
- 提交审批流程

**步骤2**: HR 管理员审批
- 审查权限需求是否合理
- 确认是否符合公司政策
- 同意或拒绝

**步骤3**: 系统管理员审批
- 最终审批权限级别
- 评估安全风险
- 同意或拒绝

**步骤4**: 分配角色（v2.1 组织级隔离）
- 选择合适的系统角色
- **指定组织**：为用户在特定组织分配角色
  - 示例：在 FF China 分配 "HR管理员" 角色
  - 用户只能管理该组织的数据
- 或分配全局角色（organizationId = null）
  - 示例：系统管理员
  - 可访问所有组织的数据
- **权限范围（Scope）**：
  - `own`：仅自己的数据（如普通员工查看自己的信息）
  - `department`：本部门数据（如部门经理管理本部门员工）
  - `organization`：本组织数据（如HR管理员管理本组织所有员工）
  - `all`：全局数据（如系统管理员管理所有组织）
- **实际示例**：
  - 部门经理：`user:read:department`（查看本部门用户）
  - HR管理员：`user:update:organization`（更新本组织用户）
- 系统管理员：`organization:create`（全局权限）
- 权限立即生效

**步骤5**: 通知
- 系统发送邮件和站内信
- 告知权限已生效
- 提供权限使用指南

**步骤6**: 验证
- 员工登录系统测试新权限
- 确认功能可用
- 如有问题反馈给管理员

### 异常路径

#### 异常 5.1: 审批被拒绝

**触发条件**: HR 或系统管理员拒绝权限申请

**系统行为**: 关闭审批流程，通知申请人

**用户反馈**: "您的权限申请已被拒绝，原因：XXX"

---

### 后置条件

- ✅ UserRole 记录已创建（包含 organizationId）
- ✅ 用户权限已生效
- ✅ 通知已发送
- ✅ 审计日志已记录权限分配

### 业务规则

1. **组织隔离**: v2.1 角色必须指定组织（organizationId），或设为 null（全局角色）
2. **权限即时生效**: 角色分配后立即生效，无需重新登录
3. **审计追踪**: 所有权限变更必须记录审计日志
4. **最小权限原则**: 只分配完成工作所需的最小权限

---

## 🎯 场景 6: 创建新组织（v2.0 核心功能）

### 场景描述

**用户角色**: 系统管理员

**使用时机**: 公司在新地区成立独立法人实体，需要在系统中创建对应的组织

**目标**: 创建独立的组织实体，配置法人信息、区域关联和组织属性

### 前置条件

- [x] 用户拥有 `organization:create` 权限（全局权限）
- [x] 已获得新组织的法人信息和注册资料
- [x] 已确定组织的主要区域和运营区域
- [x] 相关区域已在系统中创建

### 正常路径

| 步骤 | 用户操作 | 系统响应 | 页面/状态 |
|------|---------|---------|----------|
| 1 | 导航到组织管理页面 | 显示组织列表 | 组织管理页 |
| 2 | 点击「新建组织」按钮 | 打开创建组织表单 | 创建组织对话框 |
| 3 | 填写组织基本信息 | 实时验证字段唯一性 | 表单页面 |
| 4 | 填写法人信息 | 验证统一社会信用代码格式 | 表单页面 |
| 5 | 选择主要区域 | 加载区域列表 | 表单页面 |
| 6 | 选择运营区域 | 支持多选区域 | 表单页面 |
| 7 | 设置组织负责人（可选） | 加载用户列表 | 表单页面 |
| 8 | 点击「确认创建」 | 创建组织并记录日志 | 组织列表页 |

**详细说明**：

**步骤1-2**: 进入组织管理
- 系统管理员点击侧边栏「系统管理」→「组织管理」
- 系统显示所有组织列表，包含组织代码、名称、主要区域

**步骤3**: 填写基本信息
- 组织代码（必填）：全局唯一，如 "FF_CHINA"
- 组织名称（必填）：法人全称，如 "法拉第未来（中国）有限公司"
- 组织简称（选填）：如 "FF China"
- 系统自动校验代码和名称的唯一性

**步骤4**: 填写法人信息
- 法人名称（必填）：法定代表人姓名
- 统一社会信用代码（必填）：18位代码，如 "91110000MA01234567"
- 注册地址（选填）
- 系统验证信用代码格式

**步骤5-6**: 配置区域关联
- 主要区域（必填）：选择一个主要运营区域（如 CN）
- 运营区域（可选）：可添加多个运营区域
- 运营区域用于权限过滤和数据路由

**步骤7**: 设置组织负责人
- 可选择一个用户作为组织负责人
- 负责人通常是该组织的 CEO 或总经理
- 影响审批流程和权限分配

**步骤8**: 完成创建
- 系统创建 Organization 记录
- 创建 OrganizationRegion 关联记录
- 记录审计日志
- 返回组织列表页，新组织显示在列表中

### 异常路径

#### 异常 6.1: 组织代码或名称已存在

**触发条件**: 输入的组织代码或名称与已有组织重复

| 步骤 | 用户操作 | 系统响应 | 错误处理 |
|------|---------|---------|---------|
| 3 | 填写已存在的代码/名称 | 显示验证错误 | 提示"组织代码 'XX' 已存在，请使用其他代码" |

**恢复步骤**: 
1. 修改组织代码或名称
2. 重新提交

---

#### 异常 6.2: 统一社会信用代码格式错误

**触发条件**: 输入的信用代码不符合18位格式或校验码错误

**系统行为**: 阻止提交，显示错误提示

**用户反馈**: "统一社会信用代码格式错误，请输入18位有效代码"

**恢复步骤**: 修正信用代码后重新提交

---

#### 异常 6.3: 税号重复

**触发条件**: 输入的税号已被其他组织使用

**系统行为**: 阻止创建

**用户反馈**: "税号 'XXX' 已被组织 'YYY' 使用"

**恢复步骤**: 检查是否重复创建，或修正税号

---

### 后置条件

- ✅ Organization 记录已创建
- ✅ OrganizationRegion 关联已创建
- ✅ 组织代码、名称、税号全局唯一
- ✅ 主要区域已设置
- ✅ 组织负责人已分配（如有）
- ✅ 审计日志已记录创建操作

### 业务规则

1. **唯一性约束**: 组织代码、组织名称、统一社会信用代码、税号必须全局唯一
2. **区域关联**: 
   - 必须设置一个主要区域（primaryRegionId）
   - 可以关联多个运营区域（通过 OrganizationRegion 表）
3. **独立性**: 每个组织是独立的一等公民，拥有：
   - 独立的部门树结构
   - 独立的权限体系（v2.1）
   - 独立的组织配置
4. **初始状态**: 新创建组织默认状态为 ACTIVE
5. **权限要求**: 创建组织需要全局权限（`organization:create`）

---

## 🎯 场景 7: 区域配置

### 场景描述

**用户角色**: 系统管理员

**使用时机**: 公司拓展到新的地理区域，需要在系统中添加区域配置

**目标**: 创建区域实体，配置时区、货币等区域特定属性

### 前置条件

- [x] 用户拥有 `region:create` 权限（全局权限）
- [x] 已确定区域代码（如 CN、US、UAE）
- [x] 已确认区域的时区和货币设置

### 正常路径

| 步骤 | 用户操作 | 系统响应 | 页面/状态 |
|------|---------|---------|----------|
| 1 | 导航到区域管理页面 | 显示区域列表 | 区域管理页 |
| 2 | 点击「新建区域」按钮 | 打开创建区域表单 | 创建区域对话框 |
| 3 | 填写区域基本信息 | 实时验证代码唯一性 | 表单页面 |
| 4 | 选择时区 | 显示时区列表 | 表单页面 |
| 5 | 选择货币 | 显示货币列表 | 表单页面 |
| 6 | 点击「确认创建」 | 创建区域并记录日志 | 区域列表页 |

**详细说明**：

**步骤1-2**: 进入区域管理
- 系统管理员点击「系统管理」→「区域管理」
- 显示所有区域，包含代码、名称、时区、货币

**步骤3**: 填写基本信息
- 区域代码（必填）：使用标准国家/地区代码，如 "CN"、"US"、"UAE"
- 区域名称（必填）：如 "中国"、"美国"、"阿联酋"
- 系统自动校验代码唯一性

**步骤4**: 配置时区
- 从标准时区列表中选择
- 如：Asia/Shanghai、America/New_York、Asia/Dubai
- 影响时间显示和定时任务

**步骤5**: 配置货币
- 从货币列表中选择
- 如：CNY、USD、AED
- 影响财务相关功能

**步骤6**: 完成创建
- 系统创建 Region 记录
- 记录审计日志
- 返回区域列表

### 异常路径

#### 异常 7.1: 区域代码已存在

**触发条件**: 输入的区域代码已被使用

**系统行为**: 阻止创建

**用户反馈**: "区域代码 'CN' 已存在"

**恢复步骤**: 使用其他代码或编辑现有区域

---

### 后置条件

- ✅ Region 记录已创建
- ✅ 区域代码全局唯一
- ✅ 时区和货币已配置
- ✅ 区域可被组织关联
- ✅ 审计日志已记录

### 业务规则

1. **区域代码**: 使用标准的国家/地区代码（ISO 3166-1）
2. **唯一性**: 区域代码全局唯一
3. **删除保护**: 有关联组织的区域不能删除
4. **权限要求**: 需要全局权限（`region:create`）

---

## 🎯 场景 8: 岗位和职级管理

### 场景描述

**用户角色**: HR 管理员

**使用时机**: 建立或调整公司的职级体系，标准化员工岗位信息

**目标**: 创建标准化的岗位列表，设置职级层级

### 前置条件

- [x] 用户拥有 `position:create` 权限（组织级）
- [x] 已规划公司的职级体系
- [x] 已确定岗位名称和级别

### 正常路径

| 步骤 | 用户操作 | 系统响应 | 页面/状态 |
|------|---------|---------|----------|
| 1 | 导航到岗位管理页面 | 显示岗位列表 | 岗位管理页 |
| 2 | 点击「新建岗位」按钮 | 打开创建岗位表单 | 创建岗位对话框 |
| 3 | 填写岗位信息 | 实时验证代码唯一性 | 表单页面 |
| 4 | 设置职级层级 | 显示级别说明 | 表单页面 |
| 5 | 点击「确认创建」 | 创建岗位并记录日志 | 岗位列表页 |

**详细说明**：

**步骤1-2**: 进入岗位管理
- HR 管理员点击「组织管理」→「岗位管理」
- 显示所有岗位，按职级排序

**步骤3**: 填写岗位信息
- 岗位代码（必填）：全局唯一，如 "ENG_L5"
- 岗位名称（必填）：如 "高级工程师"
- 岗位描述（选填）：职责说明

**步骤4**: 设置职级
- 职级层级（必填）：数字，如 1-10
- 数字越大职级越高
- 用于薪酬、权限等级别判断

**步骤5**: 完成创建
- 系统创建 Position 记录
- 记录审计日志
- 返回岗位列表，按职级排序

### 异常路径

#### 异常 8.1: 岗位代码已存在

**触发条件**: 输入的岗位代码已被使用

**系统行为**: 阻止创建

**用户反馈**: "岗位代码 'ENG_L5' 已存在"

---

#### 异常 8.2: 尝试删除使用中的岗位

**触发条件**: 该岗位已被用户使用

**系统行为**: 阻止删除

**用户反馈**: "无法删除岗位 'XXX'，该岗位被 5 名员工使用"

---

### 后置条件

- ✅ Position 记录已创建
- ✅ 岗位代码全局唯一
- ✅ 职级层级已设置
- ✅ 岗位可被用户选择
- ✅ 审计日志已记录

### 业务规则

1. **岗位代码**: 全局唯一
2. **职级排序**: 按 level 数字排序（升序）
3. **删除保护**: 有用户使用的岗位不能删除
4. **权限要求**: 需要组织级权限（`position:create:organization`）

---

## 🎯 场景 9: 用户登录

### 场景描述

**用户角色**: 所有用户

**使用时机**: 每次访问系统时

**目标**: 通过本地密码或 LDAP 认证安全登录系统

### 前置条件

- [x] 用户账号已创建且状态为 ACTIVE
- [x] 本地用户：已设置密码
- [x] LDAP用户：LDAP服务可用

### 正常路径

| 步骤 | 用户操作 | 系统响应 | 页面/状态 |
|------|---------|---------|----------|
| 1 | 访问系统登录页 | 显示登录表单 | 登录页 |
| 2 | 输入用户名/邮箱 | 验证格式 | 登录页 |
| 3 | 输入密码 | 隐藏显示 | 登录页 |
| 4 | 点击「登录」 | 验证凭证 | - |
| 5 | 系统验证成功 | 生成 JWT Token | - |
| 6 | 跳转到首页 | 显示用户信息和权限 | 首页 |

**详细说明**：

**步骤1-3**: 输入凭证
- 用户名/邮箱（必填）
- 密码（必填）
- 支持本地认证和 LDAP 认证

**步骤4**: 认证流程
- **本地用户**：系统验证密码哈希
- **LDAP用户**：转发到 LDAP/AD 服务器验证
- 验证用户状态（ACTIVE/SUSPENDED/TERMINATED）

**步骤5**: 生成Token
- 生成 JWT Access Token（有效期 24 小时）
- 生成 Refresh Token（有效期 7 天）
- Token 包含用户 ID、角色、权限、组织信息

**步骤6**: 跳转首页
- 显示用户信息和头像
- 加载用户权限菜单
- 记录登录日志

### 异常路径

#### 异常 9.1: 用户名或密码错误

**触发条件**: 凭证验证失败

**系统行为**: 
- 拒绝登录
- 记录失败日志

**用户反馈**: "用户名或密码错误"

**说明** (v2.1.25)：
- ⚠️ **登录失败锁定机制暂不实现**（简化设计）
- 原因：主要使用 LDAP 认证，LDAP 服务器自带锁定机制
- 本地用户建议使用强密码策略
- 建议生产环境优先使用 LDAP 认证

---

#### 异常 9.2: 账号被停用或离职

**触发条件**: 用户状态为 SUSPENDED 或 TERMINATED

**系统行为**: 拒绝登录

**用户反馈**: 
- SUSPENDED: "账号已被停用，请联系管理员"
- TERMINATED: "账号已注销"

---

#### 异常 9.3: LDAP 服务不可用

**触发条件**: LDAP用户登录时 LDAP 服务器不可达

**系统行为**: 
- 显示错误提示
- 记录错误日志
- 建议用户稍后重试

**用户反馈**: "认证服务暂时不可用，请稍后重试或联系IT支持"

---

### 后置条件

- ✅ 用户已成功登录
- ✅ JWT Token 已生成并返回
- ✅ 登录日志已记录
- ✅ 用户会话已建立

### 业务规则

1. **密码策略**:
   - 最小长度 8 位
   - 至少包含 2 种字符类型（大写、小写、数字、特殊字符）
2. **登录安全** (v2.1.25 简化)：
   - ⚠️ **登录失败锁定机制暂不实现**（简化设计）
   - 原因：主要使用 LDAP 认证，LDAP 服务器自带锁定机制
   - ✅ 本地用户建议使用强密码策略
   - ✅ 建议生产环境优先使用 LDAP 认证
3. **Token 有效期**:
   - Access Token: 24 小时
   - Refresh Token: 7 天
4. **状态检查**: INACTIVE、SUSPENDED、TERMINATED 用户无法登录
5. **认证方式** (v2.1.25 → v2.4 更新)： 
   - 本地用户：bcrypt 密码验证（仅用于测试）
   - AD 用户：LDAP/AD 认证（生产环境推荐）
   - **Entra ID SSO**（v2.4 起作为独立认证方式启用，详见场景 9a-9c）
   - 三条通道并存，密码通道始终保留作为 itadmin / SSO 故障兜底入口

---

## 🎯 场景 9a: Entra SSO 登录（v2.4 新增 · 核心路径）

### 场景描述

**用户角色**: 已在 Microsoft Entra 中激活、且本系统已存在对应 User（externalSource = entra）的员工

**使用时机**: 日常登录系统的默认通道

**目标**: 员工**不输入任何密码**，通过 Microsoft 单点登录直接进入系统

### 前置条件

- [x] 用户在 Microsoft Entra ID 中状态为激活
- [x] 本系统已存在该用户（通过 Entra 同步或本场景 9b JIT 建立）
- [x] User 表 `externalSource = 'entra'` 且 `externalId` 与 Entra `oid/sub` 一致
- [x] 用户邮箱域名在 `SSO_ALLOWED_DOMAINS` 白名单内

### 正常路径

| 步骤 | 用户操作                              | 系统响应                                                              | 页面 / 状态       |
| ---- | ------------------------------------- | --------------------------------------------------------------------- | ----------------- |
| 1    | 打开 `/login`（或被路由守卫弹回）     | 显示密码登录主页面 + 下方次按钮「Sign in with Microsoft」              | 登录页            |
| 2    | 点击次按钮「Sign in with Microsoft」  | 跳转 `GET /api/v1/auth/sso/start?redirect=<当前 URL>`                | SSO 跳转中遮罩    |
| 3    | （后端 302 至 Microsoft authorize）   | 浏览器到达 Microsoft 登录页                                          | Microsoft 域      |
| 4    | 若浏览器已有 Microsoft session        | Microsoft 秒回 → 浏览器返回 `/api/v1/auth/sso/callback?code=...`     | callback 中间态   |
| 5    | （后端完成 token 兑换 + email 匹配）  | 命中现有 User，签发 JWT，302 至 `redirect` 目标                       | 完成中遮罩 → 首页 |
| 6    | -                                     | AuditLog 写入 `action=SSO_LOGIN_SUCCESS`                              | -                 |

### 成功标准

- 员工**完全没有输入密码**就进入了系统。
- 浏览器已登 Microsoft 时，从点按钮到看到首页 < 2s（视网络）。

### 异常路径

#### 异常 9a.1: 浏览器未登 Microsoft

**触发条件**: 浏览器无 Microsoft session

**系统行为**: Microsoft 登录页要求输入账号 + 密码 + MFA（如启用）

**用户反馈**: 在 Microsoft 域完成登录后回到本系统首页

**说明**: MFA / Conditional Access 完全由 Entra 处理，本系统**不做额外校验**。

---

#### 异常 9a.2: callback 校验失败

**触发条件**: id_token 签名/audience/issuer/过期校验任一不通过

**系统行为**: 后端 302 至 `/login?ssoError=SSO_TOKEN_INVALID`

**用户反馈**: 错误 Toast「SSO 登录校验失败，请重试」（i18n key `auth.sso.error.tokenInvalid`），登录页密码表单 + Microsoft 次按钮均保持可用

---

#### 异常 9a.3: email 已存在但 externalId 不一致

**触发条件**: User 表中已有同 email 用户，但其 `externalId` ≠ token 中的 `oid/sub`（可能是历史脏数据或人为篡改）

**系统行为**: 拒绝登录，**不自动覆盖** externalId，后端 302 至 `/login?ssoError=SSO_BINDING_CONFLICT`

**用户反馈**: 错误 Toast「SSO 账号绑定冲突，请联系 itadmin」（i18n key `auth.sso.error.bindingConflict`）

---

### 后置条件

- ✅ JWT Access Token + Refresh Token 已签发并写入 AuthStore
- ✅ AuditLog 记录 `SSO_LOGIN_SUCCESS`，actor = 用户 ID
- ✅ 用户进入 `redirect` 目标页面（或首页）

### 业务规则

1. **MFA / Conditional Access 完全交由 Entra 处理**，前端不做二次校验（关键产品决策）
2. **redirect 安全**: 仅允许同源相对路径，拒绝跨域跳转
3. **state 校验**: SSO start 阶段写入 state cookie，callback 阶段强校验，防 CSRF
4. **AuditLog 强制写入**: 成功登录必须留痕，作为用户详情页「最近 SSO 登录时间」的数据源

---

## 🎯 场景 9b: 首次 SSO 触发 JIT 建账号（v2.4 新增）

### 场景描述

**用户角色**: 在 Microsoft Entra 中激活、但本系统**尚未存在**对应 User 的新员工

**使用时机**: 新员工入职后首次访问系统

**目标**: 员工首次 SSO 登录时，系统**自动创建本地账号**（Just-In-Time provisioning），免去 IT 手工建号步骤

### 前置条件

- [x] 用户在 Microsoft Entra 中已激活并配置 email claim
- [x] 本系统**不存在**该 email 对应的 User
- [x] Entra SSO 通道已启用

### 正常路径（分支 1: 域名在白名单）

| 步骤 | 用户操作                       | 系统响应                                                                                                       | 页面 / 状态       |
| ---- | ------------------------------ | -------------------------------------------------------------------------------------------------------------- | ----------------- |
| 1    | 点击「用微软账号登录」         | 跳转 Entra authorize                                                                                           | SSO 跳转中遮罩    |
| 2    | 在 Microsoft 域完成登录        | 浏览器返回 callback                                                                                            | callback 中间态   |
| 3    | -                              | 后端校验 token → 在 User 表按 email 查找未命中                                                                 | -                 |
| 4    | -                              | 检查 email 域名 ∈ `SSO_ALLOWED_DOMAINS` ✅                                                                     | -                 |
| 5    | -                              | **JIT 建账号**: 创建 User（`externalSource='entra'`, `externalId=oid`, 默认角色 Employee, 默认组织从配置取值） | -                 |
| 6    | -                              | 签 JWT，写 AuditLog（`action=SSO_JIT_CREATED` + `SSO_LOGIN_SUCCESS`）                                          | -                 |
| 7    | -                              | 302 至首页                                                                                                     | 完成中遮罩 → 首页 |

### 异常路径

#### 异常 9b.1: 域名不在白名单（分支 2）

**触发条件**: email 域名 ∉ `SSO_ALLOWED_DOMAINS`

**系统行为**:

- **拒绝建号**，**不签 JWT**
- 后端 302 至 `/login?ssoError=SSO_DOMAIN_NOT_ALLOWED`
- AuditLog 记录认证失败（`status=FAILED`，`why='SSO_DOMAIN_NOT_ALLOWED'`，actor=email；本期不引入独立 `SSO_DOMAIN_REJECTED` audit action）

**用户反馈**: 错误 Toast「你的邮箱域名暂未授权使用 SSO 登录，请联系 IT 或改用密码登录」（i18n key `auth.sso.error.domainNotAllowed`）

**用户后续动作**: 找 IT 申请授权域名 / IT 手工建号 / 改走密码通道（如已有本地账号）

---

#### 异常 9b.2: Entra 账号无 email claim

**触发条件**: id_token 中无 email 字段（Entra 账号未配置邮箱）

**系统行为**: 拒绝建号，后端 302 至 `/login?ssoError=SSO_EMAIL_MISSING`

**用户反馈**: 错误 Toast「你的微软账号未配置邮箱，请联系 IT」（i18n key `auth.sso.error.emailMissing`）

---

### 后置条件

- ✅（成功分支）User 表新增一行：`source=ENTRA`, `externalSource=entra`, `externalId=<oid>`, `status=ACTIVE`, 角色 = Employee
- ✅（成功分支）AuditLog 双条记录：JIT provision + SSO login success
- ❌（拒绝分支）User 表无变化，仅 AuditLog 留痕

### 业务规则

1. **JIT 默认角色为 Employee**，**默认组织**从系统配置 `SSO_JIT_DEFAULT_ORG_ID` 读取（未配置则报错拒绝建号）
2. **email 域名白名单是 JIT 的硬门**：即使 token 校验通过，域名不允许就拒绝
3. **本期不发管理员通知邮件**: SCIM provisioning + 邮件通知归二期工单（避免本期工单膨胀）
4. **建号成功的用户即时拥有该角色的全部权限**，无需 admin 二次审批（与「员工早一秒能用系统」的产品取向对齐）
5. **externalId 一旦写入不允许通过 UI 修改**（防止账号劫持）；如确需修复走数据库脚本 + 审批流程

---

## 🎯 场景 9c: Entra 不可用时回退密码登录（v2.4 新增）

### 场景描述

**用户角色**: 任何已有本地密码的用户（含 itadmin）

**使用时机**: Microsoft Entra ID 服务故障、网络不通、或本系统与 Entra 之间链路中断时

**目标**: SSO 链路不可用不应阻塞用户登录，**密码通道作为兜底**始终可用

### 前置条件

- [x] 用户已有可用的本地密码（`source=LOCAL` 或历史导入设过密码）
- [x] 用户点击了「用微软账号登录」按钮但触发了 Entra 不可达

### 正常路径

| 步骤 | 用户操作                                     | 系统响应                                                                                  | 页面 / 状态                |
| ---- | -------------------------------------------- | ----------------------------------------------------------------------------------------- | -------------------------- |
| 1    | 点击「用微软账号登录」                       | 跳转 `/api/v1/auth/sso/start`                                                             | SSO 跳转中遮罩             |
| 2    | -                                            | 后端 discovery 请求 Entra 5xx / timeout（或浏览器 callback 阶段后端检测 token endpoint 5xx） | -                          |
| 3    | -                                            | 后端 302 至 `/login?ssoError=SSO_PROVIDER_UNAVAILABLE`                                    | 回到登录页                 |
| 4    | -                                            | 前端展示错误 Toast「Microsoft 登录暂时不可用，请改用密码登录」                            | 登录页（带 toast）         |
| 5    | 用户点击「用密码登录」                       | 展开 username/password 表单                                                               | 登录页（密码表单展开态）   |
| 6    | 输入用户名 + 密码 → 提交                     | 走 `POST /api/v1/auth/login`，沿用场景 9 主流程                                            | -                          |
| 7    | -                                            | 签 JWT，跳转 `redirect` 目标                                                              | 首页                       |

### itadmin 专属说明

- **itadmin 始终走密码通道**，不受 Entra 状态影响——这是设计层面的保护，防止 SSO 配置错误把整个系统锁死。
- 即使 itadmin 误点了「用微软账号登录」并触发了不可用错误，错误 Toast 不会阻止他再点「用密码登录」。
- itadmin 账号**不应**绑定 externalSource = entra（即使 IT 同事的 itadmin 在 Entra 也有账号，本系统的 itadmin 应保持 LOCAL）。

### 异常路径

#### 异常 9c.1: 用户没有本地密码

**触发条件**: 用户 `source=ENTRA` 或 `source=LDAP`，没有本地密码哈希

**系统行为**: 密码登录失败，返回 `IAM_INVALID_CREDENTIALS`

**用户反馈**: 「用户名或密码错误」（沿用 v2.1.1 错误提示）

**用户后续动作**: 联系 IT 重置密码 / 等待 Entra 恢复 / 联系 itadmin 临时建本地密码

---

### 后置条件

- ✅（成功）用户通过密码通道登录进入系统，行为与场景 9 完全一致
- ✅ AuditLog 记录认证失败（`status=FAILED`，`why='SSO_PROVIDER_UNAVAILABLE'`，故障留痕）+ `LOGIN_SUCCESS`（密码登录成功）

### 业务规则

1. **双通道完全并存**: SSO 故障不允许影响密码通道的可用性
2. **itadmin 必须走密码通道**: 防止 SSO 配置错误导致系统失去管理入口
3. **错误 Toast 不阻塞密码登录入口**: 即使 SSO 错误占据视觉中心，密码按钮始终可点击
4. **Entra 恢复后不需任何手工操作**: SSO 通道自动恢复可用，无需重启服务或清缓存
5. **「Entra 禁用员工立即失效」本期不实现**: 本期 SSO 失效依赖 JWT 自然过期 + 下次登录拒绝；SCIM 实时同步归二期工单
6. **⚠ Entra 禁用员工仍可通过密码通道登录（已知 bypass）**：本期 SSO 失效仅断 SSO 通道，被 Entra 禁用的员工如仍有本地密码可继续登录；运营禁用 Entra 时**必须同步在本系统将用户 status 改为 SUSPENDED**，否则存在登录绕道。二期 SCIM 实时同步关闭此 bypass

### 9b 补充异常分支

#### 异常 9b.3: `SSO_JIT_DEFAULT_ORG_ID` 配置的默认 org 已被软删

**触发条件**: 启动期 fail-fast 校验通过，但运行时 JIT 触发时再查 `Organization` 发现 `deletedAt IS NOT NULL` 或行不存在

**系统行为**: 拒绝建号，事务回滚，后端 302 至 `/login?ssoError=SSO_PROVIDER_UNAVAILABLE`

**用户反馈**: 错误 Toast「服务暂时不可用，请稍后再试或联系 IT」

**运维动作**: 监控告警 + 检查默认 org 是否被误删

---

#### 异常 9b.4: 默认 Employee 角色不存在

**触发条件**: 启动期种子数据缺失或被人为删除，`Role { code: 'employee' }` 查不到

**系统行为**: 拒绝建号，事务回滚，后端 302 至 `/login?ssoError=SSO_PROVIDER_UNAVAILABLE`

**运维动作**: 立即跑种子脚本 `npm run db:seed` 恢复角色字典

---

#### 异常 9b.5: Entra callback 携带 `query.error`

**触发条件**: Entra 回调 URL 含 `?error=access_denied|consent_required|interaction_required|...`（用户取消 / 需重新授权 / 其它）

**系统行为**:

- `access_denied` → 后端 302 至 `/login?ssoError=SSO_USER_CANCELLED`
- `consent_required` / `interaction_required` → 后端 302 至 `/login?ssoError=SSO_CONSENT_REQUIRED`
- 其它 → 后端 302 至 `/login?ssoError=SSO_PROVIDER_REJECTED`

**用户反馈**: 见 [05-ui-interaction-spec.md](./05-ui-interaction-spec.md) 错误 Toast 双语文案

---

## 🎯 场景 9d: 已存在用户首次 SSO 触发绑定回填（v2.4 新增）

### 场景描述

**用户角色**: 本系统已存在的用户（来源 LOCAL / 之前 IT 手工建号），email 与 Entra `email` claim 匹配，但 `externalId IS NULL`

**使用时机**: 首次走 SSO 通道登录

**目标**: 系统自动回填 `externalId` + `externalSource='entra'`，**不**触发 JIT 建号；下次走 SSO 即走 9a 路径

### 前置条件

- [x] User 表存在该 email 的行
- [x] `externalId IS NULL`（未绑定过任何 SSO 来源）
- [x] `status = ACTIVE`

### 正常路径

| 步骤 | 用户操作                       | 系统响应                                                                                                       | 页面 / 状态       |
| ---- | ------------------------------ | -------------------------------------------------------------------------------------------------------------- | ----------------- |
| 1    | 点击「使用 Microsoft 登录」    | 跳转 Entra authorize                                                                                           | SSO 跳转中遮罩    |
| 2    | 在 Microsoft 域完成登录        | 浏览器返回 callback                                                                                            | callback 中间态   |
| 3    | -                              | 后端校验 token → email 命中 User → `externalId IS NULL`                                                        | -                 |
| 4    | -                              | **CAS 回填**: `UPDATE users SET externalId=oid, externalSource='entra' WHERE id=$1 AND externalId IS NULL`     | -                 |
| 5    | -                              | 写 AuditLog: `SSO_BINDING_FILLED` + `SSO_LOGIN_SUCCESS`（含 `path: binding_filled`）                            | -                 |
| 6    | -                              | 签 JWT，302 至业务页（默认 `/overview`）                                                                       | 完成中遮罩 → 业务页 |

### 异常路径

#### 异常 9d.1: 并发回填，CAS 受影响行 = 0

**触发条件**: 同一 email 用户在多个浏览器 / tab 同时走 SSO，前一个事务已回填

**系统行为**: 重新查 User，若 `externalId === oid` 视为成功（同人同 oid 幂等），继续签 JWT；若 `externalId !== oid` 走冲突分支（9a.3）

**用户反馈**: 透明，正常进入系统

---

### 后置条件

- ✅ User 行：`externalId=<oid>` / `externalSource='entra'`，**不**修改 `source`
- ✅ AuditLog 双条：`SSO_BINDING_FILLED` + `SSO_LOGIN_SUCCESS`

---

## 🎯 场景 9e: LDAP 同步用户首次 SSO 自动升级绑定（v2.4 新增）

### 场景描述

**用户角色**: 历史通过 LDAP 同步建号的用户（`externalSource='ldap'`，`externalId=<LDAP_DN>`），后续公司迁移到 Entra ID

**使用时机**: LDAP 同步用户首次走 SSO 通道

**目标**: 系统自动**覆盖** `externalId` 为 Entra `oid`，`externalSource` 改 `'entra'`，并写专属 audit `SSO_BINDING_UPGRADED_FROM_LDAP`；**不**返 409 冲突

### 前置条件

- [x] User 表存在该 email 的行
- [x] `externalSource = 'ldap'`
- [x] `externalId` 非空且 ≠ 当前 Entra `oid`
- [x] `status = ACTIVE`

### 正常路径

| 步骤 | 用户操作                       | 系统响应                                                                                                                                          | 页面 / 状态       |
| ---- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| 1    | 点击「使用 Microsoft 登录」    | 跳转 Entra authorize                                                                                                                              | SSO 跳转中遮罩    |
| 2    | 在 Microsoft 域完成登录        | 浏览器返回 callback                                                                                                                               | callback 中间态   |
| 3    | -                              | 后端校验 token → email 命中 User → `externalSource='ldap'` 且 `externalId ≠ oid`                                                                  | -                 |
| 4    | -                              | **覆盖更新**: `UPDATE users SET externalId=oid, externalSource='entra' WHERE id=$1`（事务内）                                                     | -                 |
| 5    | -                              | 写 AuditLog: `SSO_BINDING_UPGRADED_FROM_LDAP`（含 `previousExternalId=<LDAP_DN>`, `newExternalId=<oid>`） + `SSO_LOGIN_SUCCESS`（`path: ldap_upgraded`） | -                 |
| 6    | -                              | 签 JWT，302 至业务页                                                                                                                               | 完成中遮罩 → 业务页 |

### 后置条件

- ✅ User 行：`externalId=<oid>` / `externalSource='entra'`（覆盖 LDAP 历史值）
- ✅ AuditLog 双条：`SSO_BINDING_UPGRADED_FROM_LDAP` + `SSO_LOGIN_SUCCESS`
- ✅ 下次走 SSO 自动走 9a 路径（已绑定 Entra）

### 业务规则

1. **仅 LDAP → Entra 升级允许自动覆盖**：与 Entra → Entra 冲突（9a.3）严格区分；后者拒绝并返 409
2. **审计可追溯**：`previousExternalId` 保留 LDAP DN，便于后续追溯历史绑定
3. **不影响 LDAP 同步 cron**：v2.4 不下线 LDAP 通道，cron 仍可运行；但 SSO 路径优先权高于 LDAP（先到先得）

---

## 🎯 场景 10: 修改密码

### 场景描述

**用户角色**: 普通员工（本地用户）

**使用时机**: 定期更新密码、忘记密码后重置、首次登录修改初始密码

**目标**: 安全更新账号密码

> **注意**: 仅本地用户可修改密码，LDAP 和 Entra ID 用户密码由外部系统管理

### 前置条件

- [x] 用户已登录
- [x] 用户为本地用户（source = LOCAL，非 LDAP/ENTRA）
- [x] 用户知道当前密码

### 正常路径

| 步骤 | 用户操作 | 系统响应 | 页面/状态 |
|------|---------|---------|----------|
| 1 | 进入个人设置页面 | 显示用户信息 | 个人设置页 |
| 2 | 点击「修改密码」 | 打开修改密码表单 | 修改密码对话框 |
| 3 | 输入当前密码 | 验证格式 | 表单页面 |
| 4 | 输入新密码 | 实时验证强度 | 表单页面 |
| 5 | 确认新密码 | 验证两次输入一致 | 表单页面 |
| 6 | 点击「确认修改」 | 验证并更新密码 | - |
| 7 | 显示成功提示 | 提示下次登录使用新密码 | 个人设置页 |

**详细说明**：

**步骤1-2**: 进入修改密码
- 用户点击头像 → 「个人设置」 → 「安全设置」
- 系统检查用户身份源（source 字段）
- LDAP 和 Entra ID 用户不显示修改密码选项，显示提示信息

**步骤3**: 验证当前密码
- 输入当前密码
- 系统验证密码正确性
- 防止未授权修改

**步骤4-5**: 设置新密码
- 新密码要求：
  - 最小长度 8 位
  - 至少包含 2 种字符类型（大写、小写、数字、特殊字符）
  - 不能与旧密码相同
- 实时显示密码强度（弱/中/强）
- 确认密码必须一致

**步骤6-7**: 更新密码
- 系统使用 bcrypt 加密新密码
- 更新 passwordHash 字段
- 使旧 Token 失效（可选）
- 记录审计日志

### 异常路径

#### 异常 10.1: 当前密码错误

**触发条件**: 输入的当前密码不正确

**系统行为**: 阻止修改

**用户反馈**: "当前密码错误，请重新输入"

---

#### 异常 10.2: 新密码不符合要求

**触发条件**: 新密码太弱或不符合策略

**系统行为**: 阻止修改，显示具体要求

**用户反馈**: "密码必须至少8位，包含大写、小写或数字中的至少2种"

---

#### 异常 10.3: 两次密码输入不一致

**触发条件**: 新密码和确认密码不匹配

**系统行为**: 阻止修改

**用户反馈**: "两次输入的密码不一致"

---

#### 异常 10.4: LDAP/Entra ID 用户尝试修改密码

**触发条件**: LDAP 或 Entra ID 用户访问修改密码功能

**系统行为**: 拒绝操作

**用户反馈**: 
- LDAP用户："LDAP 用户不能通过本系统修改密码，请联系 IT 管理员或通过 AD 域控制器修改"
- Entra ID用户："Entra ID 用户不能通过本系统修改密码，请通过 Microsoft 365 门户修改"

---

### 后置条件

- ✅ 密码已更新（passwordHash 字段）
- ✅ 审计日志已记录密码修改操作
- ✅ 用户收到修改成功通知
- ✅ 旧 Token 失效（可选策略）

### 业务规则

1. **密码策略**:
   - 最小长度 8 位
   - 至少包含 2 种字符类型（大写、小写、数字、特殊字符）
   - 使用 bcrypt 加密存储
2. **权限控制**:
   - 用户只能修改自己的密码
   - 仅本地用户（source = LOCAL）可以修改密码
   - LDAP 用户密码由 LDAP/AD 系统管理
   - Entra ID 用户密码由 Microsoft 365 管理
3. **安全措施**:
   - 必须验证当前密码
   - 新旧密码不能相同
   - 记录密码修改日志
   - 可选：修改后强制重新登录

---

## 🎯 场景 11: 配置流程角色

### 场景描述

**用户角色**: 审批流程管理员 / 系统管理员

**使用时机**: 设置审批流程时，需要配置动态审批人解析规则

**目标**: 创建流程角色（如"直属上级"、"部门经理"），配置解析规则

### 前置条件

- [x] 用户拥有 `workflow:configure` 权限
- [x] 已规划审批流程结构
- [x] 已确定需要的流程角色类型

### 正常路径

| 步骤 | 用户操作 | 系统响应 | 页面/状态 |
|------|---------|---------|----------|
| 1 | 导航到流程角色配置页 | 显示流程角色列表 | 流程角色页 |
| 2 | 点击「新建流程角色」 | 打开创建表单 | 创建流程角色对话框 |
| 3 | 填写角色基本信息 | 实时验证代码唯一性 | 表单页面 |
| 4 | 选择规则类型 | 显示对应配置选项 | 表单页面 |
| 5 | 配置解析规则 | 验证规则有效性 | 表单页面 |
| 6 | 设置兜底策略（可选） | 选择解析失败时的处理 | 表单页面 |
| 7 | 点击「确认创建」 | 创建流程角色 | 流程角色列表页 |

**详细说明**：

**步骤1-3**: 创建流程角色
- 角色代码（必填）：全局唯一，如 "DIRECT_MANAGER"
- 角色名称（必填）：如 "直属上级"
- 角色描述（选填）：说明角色用途

**步骤4-5**: 配置规则类型

**规则类型1: 组织关系解析**
- 类型：ORGANIZATIONAL_RELATION
- 配置：
  - 关系类型：DIRECT_MANAGER（直属上级）、DEPARTMENT_HEAD（部门负责人）
  - 部门上下文：可指定特定部门或使用提交人主部门
- 解析时机：审批流程提交时
- 示例：提交人的直属上级作为审批人

**规则类型2: 系统角色映射**
- 类型：SYSTEM_ROLE_MAPPING
- 配置：
  - 映射角色：选择系统角色（如 HR_MANAGER）
  - 组织范围：全局或特定组织
- 解析时机：审批流程提交时
- 示例：HR 管理员作为审批人

**规则类型3: 固定用户列表**
- 类型：FIXED_USERS
- 配置：
  - 用户列表：选择特定用户
  - 审批方式：任意一人审批或全部审批
- 解析时机：流程创建时
- 示例：指定 CEO 作为最终审批人

**步骤6**: 兜底策略（待完善）
- 解析失败时的处理方式
- 当前版本：提示管理员手动指定
- 计划 v2.2：支持自动升级到上级部门负责人

**步骤7**: 保存和测试
- 系统创建 WorkflowRole 记录
- 建议创建测试流程验证解析规则
- 记录审计日志

### 异常路径

#### 异常 11.1: 角色代码已存在

**触发条件**: 输入的角色代码已被使用

**系统行为**: 阻止创建

**用户反馈**: "流程角色代码 'DIRECT_MANAGER' 已存在"

---

#### 异常 11.2: 规则配置无效

**触发条件**: 规则配置不完整或矛盾

**系统行为**: 阻止保存

**用户反馈**: "请完整配置组织关系类型"

---

### 后置条件

- ✅ WorkflowRole 记录已创建
- ✅ 解析规则已配置
- ✅ 流程角色可被审批流程使用
- ✅ 审计日志已记录

### 业务规则

1. **规则类型**:
   - 组织关系：基于组织架构动态解析
   - 系统角色映射：映射到系统角色的所有用户
   - 固定用户：指定固定的用户列表
2. **解析时机**: 审批流程在提交时解析并固化审批人
3. **上下文支持**: 支持指定部门上下文，优先使用指定部门的汇报关系
4. **兜底策略**: 计划 v2.2 完善（当前版本提示管理员手动处理）
5. **权限要求**: 需要全局权限（`workflow:configure`）

### 使用示例

**示例1: 直属上级审批**
```
流程角色: "直属上级"
规则类型: ORGANIZATIONAL_RELATION
配置: { relationType: "DIRECT_MANAGER", useSubmitterPrimaryDept: true }
效果: 自动找到提交人在主部门的直属上级
```

**示例2: HR审批**
```
流程角色: "HR审批人"
规则类型: SYSTEM_ROLE_MAPPING
配置: { systemRole: "HR_MANAGER", organizationScope: "SUBMITTER_ORG" }
效果: 提交人所属组织的所有HR管理员
```

**示例3: CEO终审**
```
流程角色: "CEO"
规则类型: FIXED_USERS
配置: { userIds: ["ceo-user-id"], approvalType: "ANY_ONE" }
效果: 指定CEO作为最终审批人
```

---

## 📊 场景关系图

### 场景流程图

```mermaid
graph TD
    A[新员工入职] --> B[配置部门归属]
    A --> C[分配初始权限]
    
    D[外部同步] --> A
    D --> E[更新用户信息]
    
    F[组织架构调整] --> G[创建新部门]
    F --> H[调整部门层级]
    F --> I[转移员工]
    
    I --> B
    
    J[权限申请] --> K[HR 审批]
    K --> L[管理员审批]
    L --> M[分配角色]
    M --> C
    
    B --> N[设置汇报关系]
    N --> O[审批流程可用]
    
    P[创建新组织] --> F
    P --> Q[配置区域]
    
    Q --> R[区域配置]
    
    S[岗位管理] --> B
    
    T[用户登录] --> U[修改密码]
    
    V[流程角色配置] --> O
```

### 场景依赖关系

| 场景 | 依赖场景 | 说明 |
|------|---------|------|
| 场景1: 新员工入职 | 场景6: 创建新组织 | 用户必须归属某个组织 |
| 场景1: 新员工入职 | 场景7: 区域配置 | 组织关联区域，用户继承 |
| 场景1: 新员工入职 | 场景8: 岗位管理 | 用户需要选择岗位 |
| 场景2: 多部门归属 | 场景1: 新员工入职 | 必须先有用户才能配置多部门 |
| 场景3: Entra ID 同步 | 无 | 独立场景，可创建新用户 |
| 场景4: 组织架构调整 | 场景6: 创建新组织 | 部门必须归属组织 |
| 场景5: 权限申请 | 场景1: 新员工入职 | 必须先有用户才能申请权限 |
| 场景6: 创建新组织 | 场景7: 区域配置 | 组织必须关联区域 |
| 场景7: 区域配置 | 无 | 独立场景，系统基础数据 |
| 场景8: 岗位管理 | 无 | 独立场景，可单独配置 |
| 场景9: 用户登录 | 场景1: 新员工入职 | 必须先有用户账号 |
| 场景10: 修改密码 | 场景9: 用户登录 | 必须先登录
| 场景11: 流程角色配置 | 场景4: 组织架构调整 | 依赖组织关系数据 |

---

## 🔗 与功能的对应关系

### 场景 → 功能映射

| 场景 | 涉及功能 | 相关页面 | API 接口 |
|------|---------|---------|---------|
| 场景1: 新员工入职 | 用户创建、部门分配、角色分配 | 用户管理、创建用户表单 | `POST /api/v1/users` |
| 场景2: 多部门归属 | 部门归属管理 | 用户详情、部门归属页 | `POST /api/v1/users/:id/departments` |
| 场景3: Entra ID 同步 | 外部同步、用户创建/更新 | 外部集成、同步结果页 | `POST /api/v1/entra/sync` |
| 场景4: 组织架构调整 | 部门创建、层级调整、员工转移 | 组织架构页 | `POST /api/v1/departments` |
| 场景5: 权限申请 | 审批流程、角色分配 | 审批页、用户管理页 | `POST /api/v1/users/:id/roles` |
| 场景6: 创建新组织 | 组织管理、区域关联 | 组织管理页 | `POST /api/v1/organizations` |
| 场景7: 区域配置 | 区域管理 | 区域管理页 | `POST /api/v1/regions` |
| 场景8: 岗位管理 | 岗位管理 | 岗位管理页 | `POST /api/v1/positions` |
| 场景9: 用户登录 | 身份认证、Token生成 | 登录页 | `POST /api/v1/auth/login` |
| 场景10: 修改密码 | 密码管理 | 个人设置页 | `POST /api/v1/auth/change-password` |
| 场景11: 流程角色配置 | 流程角色管理 | 流程角色配置页 | `POST /api/v1/workflow-roles` |

### 功能 → 场景映射

| 功能点 | 使用场景 | 优先级 |
|--------|---------|--------|
| 用户创建 | 场景1、场景3 | P0 |
| 组织管理 | 场景6 | P0（v2.0核心） |
| 多部门归属 | 场景2 | P1 |
| 角色分配 | 场景1、场景5 | P0 |
| 部门管理 | 场景4 | P0 |
| 外部同步 | 场景3 | P1 |
| 审批流程 | 场景5、场景11 | P1 |
| 区域管理 | 场景7 | P1 |
| 岗位管理 | 场景8 | P2 |
| 身份认证 | 场景9、场景10 | P0 |
| 流程角色 | 场景11 | P1 |

---

## 🎭 角色 × 场景矩阵

| 场景 | 系统管理员 | HR 管理员 | 普通员工 | 审批流程管理员 | 说明 |
|------|-----------|----------|---------|---------------|------|
| 场景1: 新员工入职 | ✅ | ✅ | ❌ | ❌ | 需要管理权限 |
| 场景2: 多部门归属 | ✅ | ✅ | ❌ | ❌ | 需要管理权限 |
| 场景3: Entra ID 同步 | ✅ | ❌ | ❌ | ❌ | 需要系统权限 |
| 场景4: 组织架构调整 | ✅ | ✅ 部分 | ❌ | ❌ | 系统管理员全权限，HR 部分权限 |
| 场景5: 权限申请 | ✅ 审批+分配 | ✅ 审批 | ✅ 申请 | ❌ | 不同角色不同职责 |
| 场景6: 创建新组织 | ✅ | ❌ | ❌ | ❌ | **全局权限**，系统管理员专属 |
| 场景7: 区域配置 | ✅ | ❌ | ❌ | ❌ | **全局权限**，系统管理员专属 |
| 场景8: 岗位管理 | ✅ | ✅ | ❌ | ❌ | 组织级权限 |
| 场景9: 用户登录 | ✅ | ✅ | ✅ | ✅ | 所有用户 |
| 场景10: 修改密码 | ✅ | ✅ | ✅ | ✅ | 本地用户（非LDAP） |
| 场景11: 流程角色配置 | ✅ | ❌ | ❌ | ✅ | **全局权限**，系统或流程管理员 |

---

## 📊 使用频率预估

| 场景 | 使用频率 | 用户数量 | 备注 |
|------|---------|---------|------|
| 场景1: 新员工入职 | 平均每天 2-5 次 | 5-10 名 HR | 入职高峰期会增加 |
| 场景2: 多部门归属 | 每周 5-10 次 | 5-10 名 HR | 按需配置 |
| 场景3: Entra ID 同步 | 每周 1-2 次 | 2-3 名 IT 管理员 | 定期同步 |
| 场景4: 组织架构调整 | 每月 1-3 次 | 3-5 名管理员 | 按组织变化需求 |
| 场景5: 权限申请 | 每天 5-10 次 | 所有员工 | 常规需求 |
| 场景6: 创建新组织 | 每年 1-3 次 | 1-2 名系统管理员 | **低频，高影响** |
| 场景7: 区域配置 | 每年 1-2 次 | 1-2 名系统管理员 | **低频，初始化时配置** |
| 场景8: 岗位管理 | 每季度 2-5 次 | 3-5 名 HR | 职级体系调整 |
| 场景9: 用户登录 | 每天数百次 | 所有用户 | **高频，核心功能** |
| 场景10: 修改密码 | 每周 10-20 次 | 所有本地用户 | 安全要求定期修改 |
| 场景11: 流程角色配置 | 每月 1-2 次 | 1-2 名流程管理员 | 流程调整时配置 |

---

## ⚠️ 边界情况

### 边界情况 1: 删除有下属的用户

**场景**: 尝试删除或停用仍有下属汇报的用户

**用户行为**: HR 管理员点击「删除」或「停用」按钮

**系统处理**: 
- 检查该用户是否是其他用户的 managerId
- 如是，阻止操作并列出所有下属

**预期结果**: 显示错误提示："用户 'XXX' 是以下 3 名员工的直属上级，请先调整汇报关系"，并提供下属列表和批量调整功能

---

### 边界情况 2: Entra ID 同步中的 TERMINATED 用户

**场景**: Entra ID 中用户重新启用，但本地已标记为 TERMINATED

**用户行为**: 执行 Entra ID 同步

**系统处理**: 
- 检测到 TERMINATED 状态
- 跳过该用户，不自动复活
- 记录到同步日志

**预期结果**: 同步日志中显示："跳过用户 'XXX'（已离职），如需恢复请手动处理"

---

### 边界情况 3: 最后一个部门归属

**场景**: 用户只有一个部门归属，尝试删除

**用户行为**: HR 管理员点击「删除」按钮

**系统处理**: 
- 检查是否为唯一归属
- 如是，提示需要先添加其他部门或删除用户

**预期结果**: 显示错误："不能删除用户的唯一部门归属"

---

## 💡 用户体验优化建议

### 优化点 1: 批量操作支持

**当前问题**: 单个处理用户效率低

**改进方案**: 
- 支持批量导入用户（CSV/Excel）
- 支持批量分配角色
- 支持批量转移部门

**预期效果**: HR 管理员处理效率提升 50%

---

### 优化点 2: 智能上级推荐

**当前问题**: 选择上级时需要手动从列表中查找

**改进方案**: 
- 基于组织架构智能推荐上级
- 显示部门负责人作为首选
- 按职级和入职时间排序

**预期效果**: 减少选择时间，降低配置错误

---

### 优化点 3: 组织架构可视化

**当前问题**: 大型组织架构树难以浏览

**改进方案**: 
- 提供组织架构图（树形、网状）
- 支持搜索和高亮
- 支持缩放和拖拽

**预期效果**: 提升组织架构的可理解性

---

## 🔗 相关文档

- [产品需求文档](./01-prd.md) - 业务背景和功能需求
- [UI 交互规范](./05-ui-interaction-spec.md) - 界面交互细节
- [API 文档](./07-api.md) - 接口定义
- [测试场景](./09-test-scenarios.md) - 测试用例
- [架构设计](./03-architecture.md) - 技术实现

---

## 📝 变更记录

| 版本 | 日期 | 修改人 | 修改内容 |
|------|------|--------|---------|
| v1.0 | 2024-11-01 | FFOA Team | 初始版本 |
| v2.0 | 2025-12-20 | FFOA Team | 适配独立 Organization 表架构 |
| v2.1 | 2025-12-26 | FFOA Team | 按模板重构，移除交互设计细节，明确 Entra ID 仅用于同步 |
| v2.1.1 | 2025-12-26 | FFOA Team | 补充完整场景（6-11），增强场景1和5，覆盖所有PRD功能 |
| v2.1.25 | 2026-01-05 | FFOA Team | 同步 PRD v2.1.25 变更：身份源优化、定时同步、AD用户创建策略 |

---

**最后更新**: 2026-01-05  
**编写者**: FFOA 产品团队  
**文档状态**: ✅ 已完成  
**覆盖率**: 100%（11个场景覆盖10个功能模块）
**版本**: v2.1.25（同步 PRD 最新变更）
