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

## 范围
- 模块：meeting-attendance / outlook sync
- 类型：E2E（MCP Playwright + 接口联调）
- 目标：验证系列会议纳管、排除单次实例、对账与筛选能力

## 执行环境
- Frontend: http://localhost:3000
- Backend: http://localhost:3001/api/v1
- 页面：`/meetingattendance/integrations/outlook`

## 用例与结果
1. 候选会议包含系列主会议（到达断言 + 稳定断言）
- 步骤：打开 Outlook Sync 页面，观察候选区数据
- 断言：候选列表可见 `seriesMaster` 与 `occurrence` 类型
- 结果：通过

2. 候选类型筛选（seriesMaster）
- 步骤：候选类型下拉选择 `Series Master`
- 断言：列表仅保留系列主会议
- 结果：通过

3. 系列纳管后排除单次 occurrence（成功断言）
- 步骤：在 `occurrence` 行点击 `Exclude occurrence`
- 断言：该行动作区从按钮变为 `Occurrence excluded`
- 结果：通过

4. 对账触发（接口断言）
- 步骤：调用 `POST /meeting-attendance/integrations/outlook/sync/reconcile`
- 断言：返回 `200`，响应包含 `{"accepted":true,"mailboxId":"..."}`
- 结果：通过

5. 已纳管列表服务端分页（接口断言）
- 步骤：调用 `GET /meeting-attendance/integrations/outlook/bindings?mailboxId=...&page=2&pageSize=20`
- 断言：返回 `200`，pagination 生效（total/page/pageSize 正确）
- 结果：通过

6. 系列排除列表读取（接口断言）
- 步骤：调用 `GET /meeting-attendance/integrations/outlook/bindings/:id/exclusions?page=1&pageSize=20`
- 断言：返回 `200`，可读到刚新增的排除记录
- 结果：通过

## 发现与备注
- 当前租户数据中存在历史绑定记录（早期版本遗留）出现 `title/startTime` 为空；不影响本轮新链路（候选、纳管、排除、对账）可用性。
- 候选类型下拉已扩展为标准类型：`single / seriesMaster / occurrence / exception`。

7. 创建人与参会人同步修复验证（本轮追加）
- 问题现象：
  - 同步会议 `creator` 错误（回退为 `itadmin`）
  - 同步会议参会人为空（`attendeeCount=0`）
- 修复点：
  - 会议/系列创建人改为优先按 Outlook `organizer.emailAddress.address` 映射本地用户
  - 手动对账时新增“已纳管会议回填”步骤，强制重抓事件详情并回写创建人与参会人
  - `calendarView/events` 拉取字段补齐 `attendees/organizer/location/bodyPreview/recurrence`
  - delta 更新时对精简事件做详情补水（hydrate）后再同步
- 验证步骤：
  - `POST /meeting-attendance/integrations/outlook/sync/reconcile`
  - 查询 `outlookMeetingBinding + meeting + meetingRequiredAttendee` 联表结果
- 验证结果（示例）：
  - 修复前：`creator=itadmin@ff.com, attendeeCount=0`
  - 修复后：`creator=hongwei.zhang@ff.com, attendeeCount=2`
- 结果：通过

## 验证命令
- `cd frontend && npm run build`
- `curl -H "Authorization: Bearer <token>" "http://localhost:3001/api/v1/meeting-attendance/integrations/outlook/candidates?page=1&pageSize=5"`
- `curl -H "Authorization: Bearer <token>" "http://localhost:3001/api/v1/meeting-attendance/integrations/outlook/sync/reconcile"`

## 结论
- 本轮顺序任务中的第 1 步（UAT）和第 2 步（候选类型筛选优化）已完成并通过验证。

## 本次追加验证（绑定接管 + 清单）
8. 强制接管绑定（MCP 页面断言）
- 步骤：在候选列表点击 `Take over`
- 断言：
  - 弹窗提示 `Take over succeeded`
  - 候选列表 `Owner` 从原绑定人变更为当前管理员（`itadmin@ff.com`）
  - `Binding Notice` 消失（显示 `-`）
- 结果：通过

9. 纳管详情字段补充（MCP 页面断言）
- 步骤：打开已纳管会议 `Details`
- 断言：
  - 详情页出现 `Owner Email`
  - 详情页出现 `Sync From`
  - 历史筛选控件可见：`Only error events / eventType / stage / start / end`
- 结果：通过

10. 上线手工检查清单（MCP 页面断言）
- 步骤：查看 `Cutover Manual Checklist` 区块
- 断言：
  - 统计卡片可见：`Legacy series candidates / Series to handle / Future meetings to handle`
  - 操作清单可见（4 条）
  - 受影响系列表格可见（seriesId/title/endDate/futureMeetings）
- 结果：通过

11. 构建与迁移验证（命令断言）
- 步骤：
  - `cd backend && npm run prisma:generate`
  - `cd backend && npm run prisma:migrate:deploy`
  - `cd backend && npm run build`
  - `cd frontend && npm run build`
- 断言：命令均成功，新增迁移 `20260302150000_meeting_attendance_outlook_binding_owner_takeover` 已应用
- 结果：通过

12. 页面减法重构验证（MCP 页面断言）
- 步骤：打开 `/meetingattendance/integrations/outlook`
- 断言：
  - 页面不再出现“Mailboxes（新增源邮箱/启停/主来源）”区块
  - 页面不再出现“Sync Settings（Cron/批次/重试参数）”区块
  - 候选会议、已纳管会议、上线清单三个核心区块仍可用
- 结果：通过

## 本次追加验证（管理员已绑定会议总览）
13. 管理员总览页可见性与功能（MCP + 构建断言）
- 步骤：
  - 访问 `/meetingattendance/integrations/outlook/bindings-all`
  - 检查会议出勤导航
  - 执行后端/前端构建
- 断言：
  - 管理员导航可见 `Bound Meetings`
  - 页面可见搜索框、列表表头、分页控件
  - 后端构建通过：`cd backend && npm run build`
  - 前端构建通过：`cd frontend && npm run build`
- 结果：通过
- 备注：当前验证会话为管理员账号；页面空数据状态显示 `No data`，属于预期。

14. 会议管理员账号页面权限验证（MCP）
- 账号：`hongwei.zhang`（Meeting Attendance Admin）
- 登录验证：
  - `hongwei.zhang@faradayfuture.com` 登录失败（401）
  - `hongwei.zhang` 登录成功
- Outlook 同步页：
  - 入口可见，页面可正常打开 `/meetingattendance/integrations/outlook`
  - 候选会议/已纳管会议/上线清单区块可见
- 已绑定会议页（管理员专属）：
  - 导航中不显示 `Bound Meetings`
  - 直接访问 `/meetingattendance/integrations/outlook/bindings-all` 显示 `Insufficient Permissions`
- 结果：通过（权限设计符合预期）

15. 候选会议可见性修复验证（MCP + API）
- 场景：`hongwei.zhang` 账号在 Outlook 有日历事件，但同步页候选为空。
- 验证步骤：
  - 登录 `hongwei.zhang` 后打开 `/meetingattendance/integrations/outlook`
  - 调用 `GET /meeting-attendance/integrations/outlook/candidates`
- 断言：
  - 候选区展示 `Mailbox: hongwei.zhang@ff.com`
  - 候选列表不再为空，可见 seriesMaster/occurrence 事件
  - 接口返回 `200`，`mailbox` 存在且 `items.total > 0`
- 结果：通过

16. 候选类型筛选与纳管回归（MCP）
- 步骤：
  - 候选类型切换为 `Series Master`
  - 点击首条候选 `Manage`
- 断言：
  - 候选行状态从 `Manage` 变为 `Managed`
  - 已纳管列表刷新后出现系列实例数据
- 结果：通过

17. 对账按钮回归（MCP）
- 步骤：点击 `Run Reconcile`
- 断言：
  - 已纳管列表 `Last Sync` 时间更新
  - 页面无报错弹窗
- 结果：通过

18. 管理员权限边界回归（MCP）
- 账号：`hongwei.zhang`（MeetingManager）
- 步骤：访问 `/meetingattendance/integrations/outlook/bindings-all`
- 断言：显示 `Insufficient Permissions`，且导航中不展示 `Bound Meetings`
- 结果：通过

19. 旧邮箱管理入口清理验证（API）
- 步骤：以 `hongwei.zhang` token 调用旧接口
  - `GET /meeting-attendance/integrations/outlook/mailboxes`
  - `POST /meeting-attendance/integrations/outlook/mailboxes`
- 断言：两者均返回 `404`（旧入口已下线）
- 结果：通过

20. 新方案候选链路回归（API + MCP）
- 步骤：
  - 调用 `GET /meeting-attendance/integrations/outlook/candidates?page=1&pageSize=5`
  - MCP 打开 `/meetingattendance/integrations/outlook`
- 断言：
  - 返回 `200`，包含 `mailbox: hongwei.zhang@ff.com`，`total=104`
  - 页面副标题更新为“自动识别当前管理员邮箱…”语义
  - 候选列表可见 `seriesMaster/occurrence` 且可执行纳管/排除操作
- 结果：通过

21. 管理员总览页权限边界回归（MCP）
- 账号：`hongwei.zhang`（MeetingManager）
- 步骤：访问 `/meetingattendance/integrations/outlook/bindings-all`
- 断言：显示 `Insufficient Permissions`，导航仅保留 `Outlook Sync`
- 结果：通过

22. 最终收口回归（MCP + API）
- 步骤：
  - 重新登录 `hongwei.zhang` 并打开 `/meetingattendance/integrations/outlook`
  - 检查候选会议、已纳管会议、上线清单三个区块
  - 抓取页面网络请求
  - 访问 `/meetingattendance/integrations/outlook/bindings-all`
- 断言：
  - 候选区显示 `Mailbox: hongwei.zhang@ff.com` 且有系列主会议/实例数据
  - 网络请求返回：
    - `GET /meeting-attendance/integrations/outlook/candidates` => `200`
    - `GET /meeting-attendance/integrations/outlook/bindings` => `200`
    - `GET /meeting-attendance/integrations/outlook/checklists/cutover` => `200`
  - 非管理员访问 `bindings-all` 显示 `Insufficient Permissions`
  - 旧接口 `GET/POST /meeting-attendance/integrations/outlook/mailboxes` 仍为 `404`
- 结果：通过

23. 构建与迁移最终验证（命令断言）
- 步骤：
  - `cd backend && npm run prisma:generate`
  - `cd backend && npm run prisma:migrate:deploy`
  - `cd backend && npm run build`
  - `cd frontend && npm run build`
- 断言：
  - 命令全部成功
  - 新迁移 `20260302213000_meeting_attendance_remove_outlook_override_mailbox` 已应用
- 结果：通过

24. 候选类型筛选全量断言（MCP）
- 步骤：
  - 进入 `/meetingattendance/integrations/outlook`
  - 候选类型依次切换：`Series Master`、`Occurrence`、`Exception`、`Single`、`All candidate types`
- 断言：
  - `Series Master` 仅出现 `Series Master`
  - `Occurrence` 仅出现 `Occurrence`
  - `Exception`、`Single` 当前返回 `No data`（与当前邮箱数据一致）
  - 回到 `All` 后同时可见 `Series Master + Occurrence`
- 结果：通过

25. 系列实例排除链路回归（MCP + API）
- 步骤：
  - 在候选区对 `2026/5/29 08:00:00` 实例点击 `Exclude occurrence`
  - 检查行状态与网络请求
- 断言：
  - 触发 `POST /meeting-attendance/integrations/outlook/bindings/{id}/exclusions` 返回 `201`
  - 该行状态变为 `Series managed + Occurrence excluded`
  - 后续候选/已纳管刷新无报错
- 结果：通过

26. 对账执行后刷新回归（MCP + API）
- 步骤：
  - 点击 `Run Reconcile`
  - 观察已纳管列表和网络请求
- 断言：
  - 触发 `POST /meeting-attendance/integrations/outlook/sync/reconcile` 返回 `200`
  - 候选与已纳管列表自动刷新成功（`GET .../candidates`、`GET .../bindings` 均 `200`）
  - 已纳管 `Last Sync` 时间推进（03:52:xx）
- 结果：通过

27. 已纳管详情 owner 回填修复验证（代码 + MCP）
- 背景：
  - 排查发现部分 `occurrence` 绑定历史数据 `ownerEmail` 为 `null`，详情展示为 `-`
- 修复：
  - 系列传播创建/更新 occurrence 绑定时，补齐 `ownerUserId/ownerEmail`
  - 详情接口与管理员总览接口增加系列主绑定 owner 回填展示
- 验证步骤：
  - `cd backend && npm run build`
  - MCP 打开“已纳管详情”弹层检查 `Owner Email`
- 断言：
  - 构建成功
  - 详情弹层显示 `Owner Email: hongwei.zhang@ff.com`，不再为 `-`
- 结果：通过

28. 会议管理员邮箱候选拉取复核（MCP）
- 账号：`hongwei.zhang / chasoFFGpt.0`
- 步骤：
  - 登录后进入 `/meetingattendance/integrations/outlook`
  - 等待候选与已纳管列表加载完成
- 断言：
  - 候选区文案显示 `Mailbox: hongwei.zhang@ff.com`
  - 候选列表存在 `Series Master + Occurrence` 数据
  - 已纳管列表分页正常（`Page 1/7`）
- 结果：通过

29. 已绑定会议页权限与菜单高亮复核（MCP）
- 步骤：
  - 以 `hongwei.zhang` 访问 `/meetingattendance/integrations/outlook/bindings-all`
  - 退出并以 `itadmin@ff.com / Admin@2024` 登录同一路径
  - 读取顶部导航 `Outlook Sync/Bound Meetings` 链接样式类
- 断言：
  - `hongwei.zhang` 被拒绝并显示 `Insufficient Permissions`
  - 管理员可访问全量列表
  - 在 `bindings-all` 路径仅 `Bound Meetings` 为激活态（蓝色），`Outlook Sync` 为非激活态（灰色）
- 结果：通过

30. 最终构建回归（命令断言）
- 步骤：
  - `cd backend && npm run build`
  - `cd frontend && npm run build`
- 断言：
  - 后端构建成功
  - 前端构建成功（Next.js 16.0.7），未出现阻断错误
- 结果：通过

31. 对账自愈与排序修复（代码 + 构建）
- 修复：
  - 对账阶段在 seriesMaster 更新时，触发系列实例补齐，支持本地手动删除后的自愈回补。
  - 系列详情及相关查询统一按 `startTime asc`（次级 `instanceNumber asc`）返回。
  - Outlook 时间解析增强：当 Graph `dateTime` 不含偏移时按 `timeZone` 转 UTC。
- 验证命令：
  - `cd backend && npm run build`
- 断言：
  - 构建成功，核心变更编译通过。
- 结果：通过

32. 同步参数收口与默认值迁移（命令断言）
- 修复：
  - 移除未使用参数 `maxRetry/retryBackoffMs`（DTO/API 文档/前端文案/DB 字段）。
  - 保留在用参数并恢复页面可配置入口：`reconcileCron/deltaBatchSize/lookaheadDays/lookbackDays/renewBeforeMinutes`。
  - 新增迁移将本地已保存 settings 重置为新默认值。
- 验证命令：
  - `cd backend && npm run prisma:migrate:deploy`
  - `cd backend && npm run prisma:generate`
  - `cd backend && npm run build`
  - `cd frontend && npm run build`
- 断言：
  - 新迁移 `20260303194000_meeting_attendance_outlook_sync_settings_cleanup` 应用成功。
  - 前后端构建均成功。
- 结果：通过

33. MCP 阻断记录（环境）
- 现象：
  - Playwright MCP 启动浏览器时报错：`正在现有的浏览器会话中打开`，导致无法创建新 persistent context。
- 影响：
  - 本轮“页面点击级验证”改以构建与接口/代码路径验证为主，未完成新增 UI 的 MCP 点击截图。
- 后续动作：
  - 关闭占用的 Chrome 会话后重跑 MCP 冒烟（对账按钮提示、同步参数保存回读）。

34. MCP 回补验证（对账提示 + 同步参数配置）
- 环境：关闭本机占用 Chrome 会话后重跑。
- 步骤：
  - `itadmin@ff.com` 登录并进入 `/meetingattendance/integrations/outlook`。
  - 点击 `Run Reconcile`。
  - 在 `Sync Settings` 中将 `Lookback Days` 改为 `9` 保存，再改回 `7` 保存。
  - 读取配置项输入框当前值。
- 断言：
  - 点击对账后出现弹窗：`Reconcile triggered and data refreshed`。
  - 保存配置后出现弹窗：`Saved successfully`。
  - 配置回读值为：
    - `reconcileCron = */10 * * * *`
    - `deltaBatchSize = 100`
    - `lookaheadDays = 180`
    - `lookbackDays = 7`
    - `renewBeforeMinutes = 1440`
- 结果：通过

35. 事件类型标准化 + 时区标准化回归（代码 + MCP）
- 修复：
  - Graph 事件类型标准化：`singleInstance + seriesMasterId` 按 `occurrence` 处理，避免系列实例误归类为单次会议。
  - 时区标准化：优先 `start.timeZone`，兜底 `end/originalStart/originalEnd`，并映射为 IANA（含 `Pacific Standard Time -> America/Los_Angeles`）。
  - 已纳管列表排序改为按 `meeting.startTime asc`。
- 验证命令：
  - `cd backend && npm run build`
  - `cd frontend && npm run build`
- MCP 步骤：
  - 使用 `hongwei.zhang / chasoFFGpt.0` 登录 `/meetingattendance/integrations/outlook`。
  - 等待候选数据加载，观察事件类型列。
  - 点击 `Run Reconcile`，确认反馈弹窗并刷新列表。
- 断言：
  - 候选列表可见 `Series Master` 与 `Occurrence`，未再出现 `singleInstance`。
  - 对账按钮显示进行中态，并出现成功弹窗 `Reconcile triggered and data refreshed`。
  - 已纳管列表按开始时间顺序展示，时间线稳定。
- 结果：通过

36. 系列页面可见性修复 + 候选聚合回归（代码 + MCP）
- 修复：
  - 系列页可见性：前后端移除仅 `isActive=true` 展示限制，确保同步系列可见。
  - 同步兜底：同步写入系列会议时，自动恢复系列为活跃并同步时区/地点。
  - 候选聚合：候选会议默认按系列主会议聚合，支持展开查看实例；批量纳管按聚合根选择。
- 验证命令：
  - `cd backend && npm run build`
  - `cd frontend && npm run build`
- MCP 步骤：
  - 使用 `hongwei.zhang / chasoFFGpt.0` 登录。
  - 打开 `/meetingattendance/series`，检查是否显示历史同步系列。
  - 打开 `/meetingattendance/integrations/outlook`，检查候选列表是否按系列聚合（`▶ 标题 (N)`）。
- 断言：
  - 系列页可见 `FF Workspace 开发同步` 系列（含实例数量与实例列表）。
  - Outlook 候选列表按系列聚合显示，默认折叠，仅在展开后显示 occurrence/exception 明细。
  - 保留对账/纳管等操作可用性。
- 结果：通过
