# v1.2 签到方式校验 - 人工验收清单（L2 + L3 合并版）

> **本文件用途**：v1.2 功能 PR #115 + L1 测试 PR #116 合并 develop 后，用户在本地 dev 环境做 L2 端到端 + L3 逐按钮 + 双语切换的**综合验收清单**。
>
> **为什么合并**：本地 backend dev 启动遇 ERR_REQUIRE_ESM 仓库基础设施旧账（见 `feedback_local_backend_esm_crash.md`），Claude 侧无法自主跑 Playwright MCP；转交用户在本地/UAT 环境按清单执行。
>
> **前置**：develop 分支 HEAD = `f7bb268c`（L1 测试）+ `f76917bb`（v1.2 功能）。
>
> **完成后动作**：逐项打勾确认 → 回到 Claude 继续推 staging PR → UAT → production。

---

## 0. 环境准备

- [ ] 本地环境能启起来：
  - `bash scripts/dev/dev.sh up`（docker DB）
  - `cd backend && npm run db:push`（同步最新 schema 含 workCity / city / enforceCheckinMode / MeetingSeriesAttendeePreference 新表）
  - `cd backend && npm run start:dev`（如遇 ESM 崩溃见 feedback）
  - `cd frontend && npm run dev`（http://localhost:3000）
- [ ] 用管理员账号登录，确认有 `user:update` / `MeetingManager` 等权限

---

## 1. 组织架构用户管理页 `/organization/members`

### 1.1 单用户 workCity 编辑（用户详情 + 编辑页）

- [ ] 进入任意用户详情页，基本信息区**新增 "工作地" 字段**（初始显示 `-`）
- [ ] 点编辑 → 表单最下方出现 **"工作地" 输入框**（`CityAutocompleteInput`）
- [ ] focus 输入框：
  - [ ] 下拉展示已有城市列表（从 users/meetings/series 聚合去重）
  - [ ] 输入 "Bei" → 过滤出含 "Bei" 的城市（如 `Beijing`）
  - [ ] 点下拉项 → 填入输入框；**下方 hint 文案**显示规则说明
- [ ] 保存 → 详情页刷新显示新值
- [ ] 切换 EN：hint 文案 + 输入框 placeholder 都应是英文（否则 fallback 中文=漏翻）

### 1.2 批量导入工作地

- [ ] 成员列表页 **页头新增 "批量导入工作地" 按钮**（地图图标）
- [ ] 点开弹窗：
  - [ ] 有模板下载按钮（点击下载 `work-city-import-template.xlsx`，两列：Email / Work City）
  - [ ] 文件选择 → 选择一个 2 列 Excel（含表头 + 3-5 行数据）→ 成功解析，toast 显示行数
  - [ ] 点**预览**：
    - [ ] 分三类展示：🟢 exact（已有城市精确命中）/ 🟡 similar（编辑距离相似）/ 🔵 new（全新）
    - [ ] 每行显示城市名 + 行数
    - [ ] similar 行显示 "系统已有：X / 编辑距离 N"
    - [ ] 每行下拉可选 `use / replace / skip`（或新增类 `add / skip`）
    - [ ] 底部"未匹配邮箱"区列出 Excel 里系统找不到的邮箱
  - [ ] 点**确定导入** → toast 显示 `新增 N、更新 M、跳过 K`；弹窗关闭
- [ ] 边界：
  - [ ] 上传空 Excel → toast "空文件或缺列"
  - [ ] 上传非 Excel 文件（如 .txt）→ 解析失败 toast
  - [ ] 文件含缩写（`LA` + 已有 `Los Angeles`）→ 归 🔵 new 不归 🟡 similar（Levenshtein 长度差 > 2 排除）
  - [ ] 文件含拼写错误（`Los Angles` + 已有 `Los Angeles`）→ 归 🟡 similar
- [ ] 切换 EN：弹窗所有文案英文

---

## 2. 会议详情页 `/meetingattendance/meetings/[id]`

### 2.1 会议地点 + 校验开关展示

- [ ] 会议信息区"地点"下方**新增两行**：
  - [ ] "会议地点"（meeting.city），未设置时显示"未设置会议地点"
  - [ ] "签到方式校验"状态 + 切换按钮
- [ ] city 空时，开关按钮禁用 + title 悬浮提示"开启前请先填写会议地点"
- [ ] city 已设 + 管理员切换：
  - [ ] 点击 → toast 成功 → 状态翻转
  - [ ] 下方参会人列表刷新，出现"允许签到方式"tag 列 + 筛选 chips

### 2.2 允许方式 tag 列

- [ ] 开关 ON 时每位参会人一个 tag：
  - [ ] **灰色边框 tag**（城市派生，workCity == meeting.city → "线下"；!= → "线上"）
  - [ ] **紫色 tag** `线下 · 系列默认` / `线上 · 系列默认`（系列级 preference 命中）
  - [ ] **蓝色实心 tag** `线下 · 会议级覆盖` / `线上 · 会议级覆盖`（会议级 override 命中；hover tooltip 显示 `原默认：X / 改为：Y`）
  - [ ] **红色 tag** `未设置 ⚠️`（用户 workCity 空）
- [ ] 开关 OFF → tag 列消失

### 2.3 tag 点击操作菜单 + 筛选 chips

- [ ] 点击某 tag 弹出菜单：`改为线下` / `改为线上` / `恢复默认`（仅 isOverridden=true 时出现）
- [ ] 点击改为线下 → toast 成功；tag 变为蓝色覆盖 tag；`defaultMode` 在 tooltip 里显示旧值
- [ ] 点击恢复默认 → 变回灰/紫色；菜单关闭
- [ ] 筛选 chips 区（tag 列上方）：
  - [ ] `全部状态` / `允许线下` / `允许线上` / `未设置` / `已调整` 5 个 chips
  - [ ] 点击某 chip → 参会人列表只显示匹配者
- [ ] 中英切换：tag 文案 + chips + 菜单全部英文

### 2.4 边界

- [ ] 非管理员用户：tag 列可见但不能点击
- [ ] 会议 city 空但 enforceCheckinMode=true（异常态，应该被 guard 拦住；如果手工改 DB 触发）：前端照常按 workCity 比较（workCity 存在 → ONLINE，workCity 空 → 红色）

---

## 3. 系列管理页 `/meetingattendance/series`

### 3.1 创建/编辑表单

- [ ] 创建表单**新增两字段一行**：
  - [ ] "系列地点"（`CityAutocompleteInput`） + hint "用于下属会议派生；保存时级联刷新"
  - [ ] "签到方式校验"复选框 + 状态文本（已开启/已关闭）+ hint
- [ ] 城市空时复选框禁用
- [ ] 保存创建 → 下属会议若有应继承 city（通过 Outlook 同步/createMeeting 路径）
- [ ] 编辑表单同上两字段 + 预填现值
- [ ] 编辑 → 改 city 或开关 → 保存后数据库下属所有 meeting 同字段同步（通过 SQL 或另开一个会议详情页查看确认）

### 3.2 "管理默认参会人" 弹窗扩展

- [ ] 点某系列"管理默认参会人"打开弹窗
- [ ] 顶部新增**搜索框**："搜索姓名或邮箱"
  - [ ] 输入 "zhang" → 过滤列表
- [ ] 顶部有**批量操作栏**：已选 N 人；按钮 `批量线下` / `批量线上` / `批量跟随工作地`
  - [ ] 未选时按钮 disabled
- [ ] 每行参会人：
  - [ ] **新增复选框**（最左）用于批量选中
  - [ ] **新增下拉**（最右）：`跟随工作地 / 线下 / 线上`
    - [ ] 改下拉 → toast "偏好已更新"
    - [ ] 选"跟随工作地" → 调 DELETE，清除偏好
- [ ] 批量设置：勾选 3 人 → 点 `批量线下` → toast "已更新 3 人"，复选框清空

---

## 4. 会议创建/编辑页 `/meetingattendance/meetings/create`

- [ ] 表单新增 "会议地点" + "签到方式校验" 两字段一行（同系列页样式）
- [ ] city 空时开关禁用
- [ ] 填完 city + 开开关 → 提交创建 → 跳转详情页，开关持久化、city 显示

---

## 5. 访客签到页 `/meetingattendance/checkin/guest`（关键流程）

### 5.1 错误码三分支（核心验收）

准备：创建测试会议 Meeting A（city=Beijing, enforceCheckinMode=true）、测试用户 UA（workCity=Beijing）、UB（workCity=Shanghai）、UC（workCity=null）。

- [ ] **033 错误（扫错码）**：
  - UA 线上二维码扫码（URL `?type=online`）→ 派生应 ON_SITE，qrType=online 不符
  - [ ] 页面显示"请使用线下/线上专属二维码"（EN: "Please use the designated on-site or online QR code"）
- [ ] **034 错误（workCity 未配）**：
  - UC 扫任何码
  - [ ] 页面显示"请联系管理员配置您的工作地"（EN 对应）
- [ ] **036 错误（meeting.city 未配，边缘态）**：
  - 手工把 Meeting A 的 city 改 null 但保持 enforceCheckinMode=true（跳过 guard）→ UA 扫码
  - [ ] 页面显示"会议未配置地点"

### 5.2 正常签到

- [ ] UA 扫 ON_SITE 码 → 签到成功
- [ ] UB 扫 ONLINE 码 → 签到成功
- [ ] 切 EN → 所有错误 + 成功文案英文

---

## 6. 报表页 `/meetingattendance/reports`

### 6.1 单场报表

- [ ] 选一场 enforceCheckinMode=true 的会议
- [ ] 顶部 4 卡片（原 3 + 新 1）："签到方式校验：已开启" + city 副文案
- [ ] 个人出勤详情表末尾多一列"允许方式"（AllowedModeTag）
- [ ] 列按来源显示不同颜色 tag
- [ ] CSV 导出 → 打开 → 末列是 `线下 (会议级覆盖)` / `线上` / `未设置` 等

### 6.2 系列报表

- [ ] 选一个系列
- [ ] 顶部 5 卡片（原 4 + 新 1）
- [ ] 低出勤率排名表末尾多两列："允许方式" + "调整次数"
- [ ] 个人出勤详情表末尾多两列相同
- [ ] 调整次数 > 0 时 hover 显示被调整会议 ID 列表
- [ ] CSV 导出含新列

### 6.3 enforceCheckinMode=false 的报表

- [ ] 选一场关着校验的会议
- [ ] 顶部卡片显示"已关闭"（灰色）
- [ ] 表格**不显示**"允许方式"+"调整次数"列（与开启时形成对比）

---

## 7. 逐按钮 × {正常/边界/无权限/空数据}

### 7.1 空数据

- [ ] 刚创建的会议（0 参会人）/ 0 签到 → 前端都不崩，显示"暂无"

### 7.2 无权限

- [ ] 用一个非管理员账号登录 → 会议详情页 tag 列**可见但 tag 不可点**
- [ ] 系列管理页"签到方式"下拉**不可改**（或整个弹窗只读）

### 7.3 i18n 覆盖（强制）

**workspace CLAUDE.md 硬要求：中英双语切换验证所有新文案**

按新 key 分组抽测（至少 2 个每组，共约 90 个 key）：

- [ ] `allowedMode.*`：线下 / 线上 / 未设置 / 3 来源 / tooltip / 筛选 chips / 操作菜单 / 调整次数
- [ ] `enforceCheckinMode.*`：label / on / off / guardTip
- [ ] `meetingDetail.cityLabel` / `cityEmpty`
- [ ] `meetingForm.cityLabel` / `cityHint` / `enforceCheckinModeHint`
- [ ] `series.formCityLabel` / `formCityHint` / 管理默认参会人弹窗全套
- [ ] `guestCheckin.wrongCheckinQrCode` / `workCityNotConfigured` / `meetingCityNotConfigured`
- [ ] `organization.workCityImport.*` 弹窗全套
- [ ] `organization.members.workCity` / `membersList.batchImportWorkCity`

切 EN 后任一位置还显示中文 → 标记 FAIL 报给 Claude。

---

## 8. Outlook 同步（如有 Outlook 纳管环境）

- [ ] 对一个已纳管 Outlook 系列改 city + 开开关
- [ ] Outlook 侧新建一个该系列的 occurrence → 同步后新 meeting 应继承 series 的 city + enforceCheckinMode
- [ ] 对已有 meeting 改 location 通过 Outlook 同步回来 → city / enforceCheckinMode **不回写**（本地维护优先）

---

## 验收完成后

**全部 ✅**：回 Claude 说"验收通过"，Claude 继续：
1. 创建 develop → staging PR + 自审批合并
2. UAT 自动部署
3. UAT 环境按 `feedback_uat_full_verification_before_prod.md` 再走一遍 MCP 验收
4. staging → production PR
5. prod 部署后再一轮验收

**有 ❌**：告诉 Claude 具体哪一项不通过，Claude 修完再推一个 hotfix PR 回到 develop，然后重新验收此项。

---

**清单版本**：2026-04-21 v1.0
**对应 PR**：#115（功能）+ #116（L1 测试）
**对应 memory**：[project_meeting_attendance_v12_whitelist.md](../../../../../home/ubuntu/.claude/projects/-home-ubuntu-Code/memory/workspace-memory/project_meeting_attendance_v12_whitelist.md)
