# Meeting Attendance Outlook Sync E2E Report (2026-03-03)

## 范围
- 模块：meeting-attendance / outlook sync
- 类型：E2E（MCP Playwright + 构建验证）
- 目标：修复并验证候选系列数量、纳管反馈、类型展示、时间展示一致性、系列参数回填

## 执行环境
- Frontend: http://localhost:3000
- Backend: http://localhost:3001/api/v1
- 账号：`hongwei.zhang`（Meeting Attendance Admin）

## 用例与结果
1. 候选系列数量验证（MCP）
- 步骤：打开 `/meetingattendance/integrations/outlook`
- 断言：候选区存在 `FF Workspace 开发同步 (66)` 系列聚合行
- 结果：通过

2. 对账触发反馈验证（MCP）
- 步骤：点击 `Run Reconcile`
- 断言：
  - 按钮状态从 `Run Reconcile` -> `Reconciling...` -> 恢复
  - 页面出现可见反馈文案 `Reconcile triggered and data refreshed`
- 结果：通过

3. 候选与已纳管类型展示验证（MCP）
- 步骤：查看候选列表与已纳管列表
- 断言：类型列使用 badge 样式区分（Series Master / Occurrence / Exception / Single）
- 结果：通过

4. 时间展示一致性验证（MCP）
- 步骤：查看候选/已纳管列表时间字段
- 断言：统一按“展示时区”渲染，卡片标题展示 `Display Timezone`
- 结果：通过

5. 取消会议纳管限制（代码回归）
- 步骤：检查后端纳管逻辑
- 断言：`isCancelled=true` 的 Outlook 事件会被后端拒绝纳管（400）
- 结果：通过

6. 构建验证
- 步骤：
  - `cd backend && npm run build`
  - `cd frontend && npm run build`
- 断言：均成功
- 结果：通过

## 代码级修复点
- 候选拉取上限修复：候选抓取量不再受 `deltaBatchSize` 过小影响，至少拉取 500 条，避免系列实例被截断。
- 候选排序/过滤时区修复：按事件时区解析时间，避免 `new Date(无偏移字符串)` 造成错序和错过滤。
- 系列参数回填修复：对账/同步命中已有系列时，回填 `pattern/frequency/endDate/maxOccurrences/timezone`，并优先使用 seriesMaster 元数据。
- 系列主会议详情补全：按 ID 拉取 seriesMaster 时补齐 `recurrence` 字段，避免结束日期/次数丢失。
- 前端反馈增强：纳管/接管/排除/对账操作增加页面内反馈文案。
- 前端展示增强：候选、已纳管、已绑定（管理员页）统一类型 badge 和时间格式化。

## 阻断/限制
- `hongwei.zhang` 非 Administrator，无法访问 `/meetingattendance/integrations/outlook/bindings-all`（该权限限制符合设计）。

## 追加回归（2026-03-03 17:00 后）
7. 已纳管筛选与系列折叠（MCP）
- 步骤：在 `/meetingattendance/integrations/outlook` 使用已纳管 `status/type` 筛选，并展开系列行。
- 断言：
  - 状态筛选可切换并只显示匹配数据。
  - 系列可 `▶/▼` 折叠展开，展开后显示子实例列表。
- 结果：通过

8. 取消纳管（MCP）
- 步骤：点击已纳管行 `Unmanage`，随后重新 `Manage` 恢复。
- 断言：
  - 页面出现成功反馈 `Unmanaged successfully`。
  - 该行从已纳管移除并回到候选可纳管状态。
  - 重新纳管后恢复为已纳管状态。
- 结果：通过

9. 已纳管时间展示一致性（二次回归，MCP）
- 步骤：触发对账并刷新已纳管列表。
- 断言：
  - 系列主会议/实例时间统一按展示时区渲染（America/Los_Angeles）。
  - 旧的 `00:00/01:00` 偏移显示已修复为 `16:00/17:00`。
- 结果：通过

10. 系列参数回填（二次回归，API+MCP）
- 步骤：触发对账，检查系列详情与编辑弹窗。
- 断言：
  - `endDate` 正确回填（示例：`2026-06-02`）。
  - `maxOccurrences` 在 endDate 型系列下不再错误显示 `1/10`，为空（null）。
- 结果：通过

## 本轮新增修复点
- 修复 Graph 日期时区解析优先级：优先 `recurrenceTimeZone/originalStartTimeZone`，避免 `start.timeZone=UTC` 导致入库偏移 8 小时。
- 修复系列范围映射规则：存在 `endDate` 时不再写入 `maxOccurrences`。
- 修复已存在系列更新时的清空逻辑：`maxOccurrences/endDate` 支持显式写 `null` 覆盖旧值，避免历史脏值残留。

## 追加回归（2026-03-03 18:30 后）
11. 同步页操作反馈统一为 toast（MCP）
- 步骤：在 `/meetingattendance/integrations/outlook` 触发 `Run Reconcile`、`Unmanage`。
- 断言：
  - 不再出现浏览器 `alert` 弹窗。
  - 页面内出现 toast 与操作结果文案（如 `Reconcile triggered and data refreshed`、`Unmanaged successfully`）。
- 结果：通过

12. 纳管/取消纳管进行中状态（MCP）
- 步骤：触发 `Run Reconcile`、`Unmanage`。
- 断言：
  - 按钮会进入禁用态并展示进行中文案（如 `Reconciling...`）。
  - 完成后恢复可点击。
- 结果：通过

13. 时区展示统一（MCP）
- 步骤：查看 Outlook 候选/已纳管时间列、系列会议实例时间。
- 断言：
  - 时间均以 `YYYY/MM/DD HH:mm (时区)` 形式显示。
  - 候选会议按事件业务时区显示（示例：`Asia/Shanghai`），并显式展示时区。
- 结果：通过

14. 删除系列会议后列表隐藏（MCP）
- 步骤：在系列页删除 `FF Workspace 开发同步`。
- 断言：
  - 删除后系列页不再展示该条。
  - 页面显示 `No active meeting series` 空态。
- 结果：通过

## 本轮新增修复点（2026-03-03 18:30）
- 同步页：移除 `window.alert`，统一改为 `toast`。
- 同步页：纳管/接管/取消纳管/排除单次/移除排除均增加进行中状态控制，避免重复点击。
- 同步页：候选与已纳管时间显示统一补充时区后缀，减少 UTC/本地混淆。
- 系列页：时间显示统一为“时间+时区”，历史弹窗不再强制按 `UTC` 展示。
- 系列页：新增系列结束规则文案，`maxOccurrences` 为空时明确按结束日期展示。
- 后端：系列列表仅返回 `isActive=true`，修复“删除后仍在系列页显示”问题。
- 后端：Outlook 已纳管列表接口补充返回会议 `timezone` 字段，支撑前端按业务时区显示。

## 追加回归（2026-03-03 19:10 后）
15. 旧方案接口残留清理回归（代码+构建）
- 步骤：移除 `GET /integrations/outlook/checklists/cutover` 对外接口链路（controller/service/frontend api）。
- 断言：
  - 后端编译通过，无引用缺失。
  - 前端编译通过，无 API 调用缺失。
- 结果：通过

16. Outlook 同步页核心流程冒烟（MCP）
- 步骤：登录 `hongwei.zhang`，打开 `/meetingattendance/integrations/outlook`。
- 断言：
  - 页面可正常加载，候选/已纳管/同步设置区块均渲染。
  - 触发类按钮存在并可交互（Refresh/Run Reconcile/Batch Manage）。
  - 无前端 console error，接口返回 200。
- 结果：通过

## 本轮新增修复点（2026-03-03 19:10）
- 移除已废弃“上线检查清单”接口链路，减少死代码与旧方案残留。
- 文档同步收口：UI 规范、API 合同、测试场景不再声明 `checklists/cutover`。
- PRD 文案更新：历史系列截断改为“迁移操作文档”执行，不再作为页面能力。

## 追加回归（2026-03-03 20:00 后）
17. 时间显示标准统一（代码+构建）
- 步骤：统一 `timezone` 工具输出格式与页面调用。
- 断言：
  - 时间格式统一为 `YYYY-MM-DD HH:mm (IANA, TZ_SHORT)`。
  - 用户时区与会议时区不一致时，展示“会议时区时间”补充信息。
- 结果：通过

18. 同步页操作反馈与进行中状态（MCP+代码）
- 步骤：检查纳管/接管/取消纳管/排除单次/取消排除按钮文案及批量纳管流程。
- 断言：
  - 行内按钮进入对应进行中文案（Managing/Unmanaging/Taking over/Excluding/Removing）。
  - 批量纳管开始时即有提示，结束后反馈成功/部分成功。
- 结果：通过

19. 会议详情页告警交互收口（代码+构建）
- 步骤：将详情页 `window.alert` 改为 toast。
- 断言：
  - 删除会议、编辑会议、更新出勤、增删参会人均不再触发浏览器原生 alert。
- 结果：通过

20. 系列删除可见性交互优化（代码）
- 步骤：系列删除改为乐观更新（先从列表移除，失败回滚）。
- 断言：
  - 删除后列表即时反馈。
  - 删除失败时回滚并提示。
- 结果：通过
21. 全局体验一致性补丁（代码）
- 步骤：会议创建页错误反馈从 `window.alert` 统一改为 toast。
- 断言：创建失败提示不再触发浏览器原生弹窗。
- 结果：通过

## 追加回归（2026-03-03 20:40 后）
22. 真实数据链路复测：纳管 -> 取消纳管 -> 对账（MCP）
- 步骤：
  - 在 Outlook 同步页对系列主会议 `FF Workspace 开发同步 (67)` 执行 `Manage`。
  - 随后在已纳管列表执行 `Unmanage`。
  - 点击 `Run Reconcile`。
- 断言：
  - `Manage` 过程出现行内进行中状态 `Managing...`，完成后出现 `Managed successfully`。
  - `Unmanage` 后出现 `Unmanaged (future sync stopped, history retained)`，已纳管列表即时清空。
  - 对账触发后出现 `Reconcile triggered and data refreshed`（页面文案 + toast）。
- 结果：通过

23. 候选系列展开与时间顺序/时区展示复测（MCP）
- 步骤：展开 `FF Workspace 开发同步 (67)` 系列候选。
- 断言：
  - 下属 occurrence 按开始时间升序展示（`2026-03-01 16:00`、`2026-03-02 16:00`、`2026-03-03 16:00` ...）。
  - 全部时间统一显示 `YYYY-MM-DD HH:mm (IANA, TZ_SHORT)`。
  - 用户时区与会议时区不一致时展示补充行：`Meeting timezone time: ...`。
- 结果：通过

24. Single 样本可测性检查（MCP）
- 步骤：候选类型切换 `Single`，并勾选 `Include past meetings` + `Include cancelled` 后刷新。
- 断言：
  - 当前真实邮箱 `hongwei.zhang@ff.com` 下 Single 候选仍为 `No data`。
- 结果：阻断（数据样本缺失）
- 说明：当前轮次无法完成“single 纳管/取消纳管/对账”实操，不是功能异常；待 Outlook 侧新增或出现单次事件样本后可补跑。

## 追加回归（2026-03-03 21:10 后）
25. 候选会议时间偏移修复验证（MCP）
- 步骤：打开 `/meetingattendance/integrations/outlook`，触发 `Run Reconcile`。
- 断言：
  - 系列候选 `FF Workspace 开发同步` 主会议时间显示为 `2026-03-02 16:00 (Asia/Shanghai, GMT+8)`。
  - 次信息显示观察者时区时间 `Viewer timezone time: 2026-03-02 00:00 (America/Los_Angeles, PST)`。
- 结果：通过

26. 单次会议全链路复测（MCP）
- 样本：`测试单次同步`（Single）
- 步骤：
  - 候选列表点击 `Manage`；
  - 在已纳管列表确认出现该单次会议；
  - 点击 `Unmanage`；
  - 点击 `Run Reconcile`。
- 断言：
  - 纳管过程出现成功反馈 `Managed successfully`；
  - 取消纳管后出现 `Unmanaged (future sync stopped, history retained)`；
  - 对账触发后出现 `Reconcile triggered and data refreshed`；
  - 单次会议在候选与已纳管列表之间状态切换正确。
- 结果：通过

27. 时间展示语义统一验证（MCP）
- 步骤：检查 Outlook 同步页候选/已纳管/系列详情时间文案。
- 断言：
  - 主时间统一按会议业务时区展示；
  - 次时间文案统一为 `Viewer timezone time`（观察者时区）；
  - 格式统一为 `YYYY-MM-DD HH:mm (IANA, TZ_SHORT)`。
- 结果：通过

## 追加回归（2026-03-03 22:00 后）
28. 系列实例页 UTC 偏移修复验证（MCP）
- 步骤：登录 `hongwei.zhang`，进入 `/meetingattendance/series`，检查 `FF Workspace 开发同步` 下实例时间。
- 断言：
  - 系列实例显示为 `16:00 (Asia/Shanghai, GMT+8)`，不再出现 `08:00` 的偏移显示。
  - 同时展示观察者时区次行（America/Los_Angeles）。
- 结果：通过

29. 真实链路复测（单次事件 + 纳管状态反馈 + 对账反馈）（MCP）
- 样本：`测试单次同步`（Single）
- 步骤：
  - 在候选列表执行 `Manage`；
  - 在已纳管列表执行 `Unmanage`；
  - 点击 `Run Reconcile`。
- 断言：
  - 行内按钮状态从 `Manage` -> `Managing...` -> `Managed`；
  - 取消纳管后出现 `Unmanaged (future sync stopped, history retained)`；
  - 对账期间按钮显示 `Reconciling...`，完成后提示 `Reconcile triggered and data refreshed`。
- 结果：通过

## 追加回归（2026-03-03 22:20 后）
27. 候选/已纳管请求去重与降载验证（MCP）
- 步骤：登录 `hongwei.zhang` 打开 `/meetingattendance/integrations/outlook`，采集 XHR 网络请求。
- 断言：
  - 候选请求变更为 `candidates?page=1&pageSize=100`（不再 500）。
  - 页面首屏仅出现一次 `settings`、一次 `candidates`、一次 `bindings` 请求（无重复并发）。
- 结果：通过

28. 候选与已纳管分页分组验证（MCP）
- 步骤：查看候选与已纳管列表分页区，并检查系列行展开。
- 断言：
  - 分页控件存在（10/20/50 + 上/下页）。
  - 系列会议以“组”为单位分页，展开后子会议连续展示，不被分页切断。
- 结果：通过

29. bindings-all 样式对齐与分组分页（代码回归）
- 步骤：对照同步页候选块实现检查 `bindings-all`。
- 断言：
  - 展开按钮样式统一为 `text-xs border rounded` + `▶/▼`。
  - 系列子行背景与缩进风格统一（`bg-gray-50/60 hover:bg-gray-100`）。
  - 已改为“先分组再前端分页”（页切换不拆系列）。
- 结果：通过（代码级）
- 限制：当前测试账号非 Administrator，页面访问受限，待管理员账号补跑 UI 实测。

## 本轮新增修复点（2026-03-03 22:20）
- 前端：Outlook 候选请求增加 in-flight 去重，避免首屏/交互期间重复并发请求。
- 前端：候选首屏拉取量 `pageSize` 从 200 下调为 100，减少首次加载体积与等待时间。
- 前端：`bindings-all` 改为分组后前端分页，避免系列被服务端分页拆断。
- 前端：`bindings-all` 系列展开样式与同步页统一（`▶/▼` 小按钮、计数、子行风格）。
- 后端：候选查询抓取上限收敛（`fetchTop/maxFetch` 降载），并下调 seriesMaster 补拉上限，降低大邮箱场景响应时延。

## 追加回归（2026-03-03 23:xx 后）
28. 候选分页稳定性回归（MCP）
- 步骤：`Include past meetings` 开启后，在候选分页执行 `1 -> 2 -> 3 -> 4 -> 3 -> 2 -> 1`。
- 断言：
  - 每页均有数据（20 行），未出现“第 4 页回第 1 页为空”。
  - 页码状态稳定（`Page 1/56` 等）。
- 结果：通过

29. 候选系列子项按需加载回归（MCP）
- 步骤：展开候选首条系列 `IT meeting`。
- 断言：
  - 展开后显示子项数量（示例：`(107)`）。
  - 子项不受列表分页影响，按展开接口单独加载。
- 结果：通过

30. 已绑定会议页（Administrator）系列展开回归（MCP）
- 账号：`itadmin@ff.com`（Administrator）
- 步骤：打开 `/meetingattendance/integrations/outlook/bindings-all`，展开 `FF Workspace 开发同步`。
- 断言：
  - 系列行样式与 Outlook 同步页一致（`▶/▼` + 标题 + 数量）。
  - 展开后可加载子项（示例从 `(18)` 更新为 `(66)`）。
- 结果：通过

31. 候选/已纳管/已绑定请求降载回归（网络观测）
- 观测：
  - 候选、已纳管、已绑定改为服务端分页（默认 `pageSize=20`），不再首屏拉取 500 条全量。
  - 翻页只请求当前页；系列实例改为“展开时单独请求子项”。
- 结果：通过

## 本轮新增修复点（2026-03-03 23:xx）
- 候选列表：新增服务端过滤参数 `includeCancelled/includePast/onlyUnmanaged/eventType`，并切换为服务端分页驱动。
- 候选列表：请求并发覆盖问题改为“请求序列”机制，避免旧请求结果覆盖新页状态。
- 后端候选：增加短 TTL 候选缓存（按 mailbox+actor+窗口），减少翻页重复打 Graph，降低 502 风险。
- 已纳管/已绑定：新增 `GET /integrations/outlook/bindings/series/:seriesMasterId/children`，系列子项改为按需加载，避免分页切断子项。
- 已纳管与已绑定：新增服务端过滤参数 `eventType/status`，分页查询与筛选一致。
