# 钉钉模块 - 测试场景文档

> **版本**: v1.0  
> **最后更新**: 2026-03-17  
> **维护者**: FFOA 测试团队

> **参考标准**: `.agents/skills/test-backend/references/testing-standards.md`

---

## 测试摘要

### 最小必填项

| 用例 | 类型 | 优先级 | 入口 | 结果 |
|------|------|--------|------|------|
| 审批撤销修复 | Integration / MCP | P1 | `/organization/dingtalk` | 待执行 |
| 年假余额洞察 | Integration / MCP | P1 | `/organization/dingtalk/annual-leave/quotas` | 待执行 |
| 年假释放计划 | Integration / MCP | P1 | `/organization/dingtalk/annual-leave/release-plan` | 待执行 |

---

## 测试说明

### 覆盖要求

- 正常场景：同步任务可触发、余额总览可读取、释放计划可查看
- 异常场景：审批号未命中、员工不存在、缺少参数
- 边界场景：最近 60 天收敛、无计划员工、无余额员工
- 权限场景：复用 `organization:sync`

## 用例详情

### 审批撤销修复

#### 场景 1：`undefined-*` 预览匹配

- 前置条件：
  - 存在指定员工的钉钉考勤审批记录
  - 记录类型与选择的同步类型一致
- 步骤：
  1. 打开 `/organization/dingtalk`
  2. 选择同步类型、员工、日期范围
  3. 输入审批号 `undefined-*`
  4. 点击预览
- 预期结果：
  - 返回命中数量
  - 不触发实际撤销

#### 场景 2：精确审批号命中后撤销

- 前置条件：
  - 已知员工 `userId`
  - 已知精确审批号真实存在
- 步骤：
  1. 输入精确审批号
  2. 点击执行撤销
- 预期结果：
  - 返回 `processedCount = 1`
  - 结果状态为 `CANCELLED`

#### 场景 3：时间窗口强制收敛到最近 60 天

- 前置条件：
  - 手动触发考勤类同步或审批修复
- 步骤：
  1. 不传 `fromTime`
  2. 传入早于最近 60 天的 `fromTime`
- 预期结果：
  - 后端自动收敛开始时间
  - 日志记录保护动作

### 年假余额洞察

#### 场景 4：手动刷新假期余额快照

- 前置条件：
  - 钉钉考勤假期余额接口可用
  - 至少存在 1 名在职员工
- 步骤：
  1. 调用 `POST /organization/dingtalk/annual-leave/quotas/refresh`
- 预期结果：
  - 返回 `employeeCount`、`recordCount`、`leaveTypeCount`、`lastSyncedAt`
  - 快照表写入当前余额记录

#### 场景 5：假期余额总览读取本地快照

- 前置条件：
  - 已执行至少一次快照刷新
- 步骤：
  1. 打开 `/organization/dingtalk/annual-leave/quotas`
- 预期结果：
  - 页面展示统计卡与余额矩阵
  - 读取本地快照，不依赖实时接口

#### 场景 6：点击余额查看详情

- 前置条件：
  - 目标员工的目标假期存在余额记录
- 步骤：
  1. 点击某个员工某个假期类型的余额单元格
- 预期结果：
  - 抽屉展示总额、已用、剩余和有效期
  - 展示释放记录和使用记录

### 年假释放计划

#### 场景 7：手动重算全部员工年假计划

- 前置条件：
  - 本地员工表中存在正常员工
- 步骤：
  1. 打开 `/organization/dingtalk/annual-leave/release-plan`
  2. 点击更新中间表
- 预期结果：
  - 仅重算本地计划
  - 不直接发放钉钉额度

#### 场景 8：释放计划页默认仅展示正常员工

- 前置条件：
  - `dingtalk_employees` 中同时存在 `正常 / 停薪留职 / 顾问 / 已离职`
- 步骤：
  1. 打开 `/organization/dingtalk/annual-leave/release-plan`
- 预期结果：
  - 仅展示 `正常` 员工
  - 特殊状态员工不出现在列表中

#### 场景 9：无计划员工保留在结果中

- 前置条件：
  - 某正常员工缺少入职日期或首次参加工作日期
- 步骤：
  1. 打开释放计划页
- 预期结果：
  - 员工仍展示在结果中
  - 返回 `hasPlan = false`
  - 返回合理的 `noPlanReason`

### 年假计划参数维护

#### 场景 10：点击员工姓名打开参数弹窗

- 前置条件：
  - 已存在本地计划或可返回默认值
- 步骤：
  1. 打开余额页或释放计划页
  2. 点击员工姓名
- 预期结果：
  - 弹窗打开
  - 展示员工姓名、`userId`、年度、总计划天数和最近重算时间
  - 仅展示 `adjustmentDays` 与 `notCountDays`

#### 场景 11：保存参数时直接重算

- 前置条件：
  - 员工具有入职日期和首次参加工作日期
- 步骤：
  1. 修改 `notCountDays`
  2. 点击保存
- 预期结果：
  - 参数更新成功
  - 触发重算
  - `releaseSchedule` 发生变化

#### 场景 12：首次读取无记录时返回默认值

- 前置条件：
  - 员工存在，但该年度尚无计划记录
- 步骤：
  1. 调用 `GET /organization/dingtalk/annual-leave/plan-settings`
- 预期结果：
  - 返回默认值结构
  - 不在读取阶段创建数据库记录
