# Zoom 通话回顾：让销售团队在网站上直接做，不用 Excel

2026-09-26 · Wai Kit

今天老板要看「6 月到现在做过的 Zoom、每个客户现在是什么等级、是谁做的 Zoom」，我们只能从数据库抽一份
Excel（`Zoom_Calls_Jun-Sep2026_Lead_Levels.xlsx`）。目标是以后销售团队打开
Manage → Zoom → Recordings 就能看到同样的东西，并且能当场把没认领的 Zoom 认领到客户。

Excel 抽出来的数字，就是这个功能要解决的问题：

| 6 月 1 日到今天 | 数量 |
| --- | --- |
| 做过的 Zoom（有 recording） | 154 |
| 已经 link 到客户 | 90 |
| 没有 link 到客户 | 64（42%） |
| 64 个没 link 的里面，AI 有给建议客户的 | 0 |
| 90 个已 link 的里面，客户在 CRM 没有 Assigned Closer 的 | 66 |
| 在系统排了 Zoom、时间过了却没有 recording 的 | 38 |

## 一个例子：陈先生

陈先生 7 月 3 日和 Zen 做了一次 Zoom。Zoom 录了音，系统把录音同步进来了，但录音的 topic 是
"Zen's Zoom Meeting"，系统不知道这是陈先生，所以这一行的 Lead 栏是空的。

销售团队想做的事只有三件：

1. 看到这一行时，知道它还没有认领（没 link 客户），一键把它认领给陈先生。
2. 认领之后，同一行直接看到陈先生现在的等级（例如 Prospect 商机进行中），不用另开 Leads 页面找。
3. 看到这一场 Zoom 是 Zen 做的，就算录音是用公司共用的 Support 账号开的。

现在系统里，第 1 件有一半（有 Link lead 按钮，但没有「只看没认领的」筛选）；第 2 件没有；第 3 件只有一半。

## 系统现在已经有的（IT 直接复用，不要重做）

- **Recordings 页面**（`/manage/zoom/recordings`）已经列出每场 Zoom：Agent（Zoom 账号主人）、Lead、Topic、时间、长度、AI 状态。
- **Link lead 按钮**已经有：打开一个小窗，用名字 / 电话 / email 搜客户，选了就 link。link 之后 sync 不会覆盖。
- **客户等级的规则**已经有：Leads 列表的 Status 栏和 `/manage/leads/stages` 那页用的是同一条规则（代码在 `LeadStages`），11 个等级，中英文说明都在。
- **AI 建议客户**的栏位已经有（`suggested_lead_id`、`suggested_lead_reason`），也有写入的方法，但总开关在 production 是关的，从来没跑过（见第 4 项）。
- **每个客户的 Leads → Show 页面**已经有 Zoom tab，列出这个客户的 Zoom。

## 要加的六项

按优先顺序排。每一项写了销售员看到什么、做什么、怎样算做好。

### 1. 「没认领 / 已认领」筛选（最优先）

- **看到**：Recordings 页面的筛选里多一个 Link status：All / Unlinked / Linked，旁边有数量（今天会显示 Unlinked 64）。
- **做**：点 Unlinked，列表只剩没 link 的 Zoom，每一行的 Link lead 按钮直接露在行上，不用先点开详情。
- **做好的标准**：销售员点 Unlinked，一行一行认领，认领完这一行就从 Unlinked 消失。

### 2. 「不是客户通话」的排除

64 个没 link 的里，24 个少于 10 分钟，36 个 topic 是 "xxx's Zoom Meeting" 这种通用名——很多是测试或内部通话，永远不会有客户可以 link。

- **看到**：Link lead 小窗多一个选项「这不是客户通话」（Not a customer call），可选原因：内部会议 / 测试 / 其他。
- **做**：点了之后这一行离开 Unlinked，进入一个新的 Excluded 筛选，随时可以改回来。
- **做好的标准**：Unlinked 的数量只剩真正等认领的。

### 3. Lead Level 等级栏

- **看到**：Recordings 列表多一栏 Level，显示客户现在的等级 badge（Client 成交客户、Prospect 商机进行中……），和 Leads 列表的 Status 栏一模一样。滑鼠停上去显示中文一行说明。没 link 的行显示「—」。
- **重要**：等级是「现在」的等级，不是 Zoom 当天的。列表标题要写明 "Level today"。
- **做好的标准**：同一个客户，Recordings 页面的 Level 和 Leads 列表的 Status 永远一样（因为用同一条规则，不是另抄一份）。

### 4. AI 自动建议客户，销售一键确认（功能已经写好，production 没开）

有 48 个没 link 的 Zoom 是有 transcript（文字稿）的，文字稿里通常有客户自己报的名字和电话。

这个功能 7 月 30 日就做好了：每小时自动扫一次没 link、有 transcript 的 Zoom，AI 从文字稿找出客户报的名字
/ 电话 / email，再去 CRM 找到唯一符合的客户，写成「建议」；Zoom → AI Agent 页面有确认 / 不是他的按钮。
但它有一个总开关（环境设定 `ZOOM_LEAD_SUGGEST`），production 上是关的，所以从来没跑过一次——production
数据库里 `lead_suggested_at` 全部是空的。

- **要做的第一步**：production 打开开关，每小时最多跑 10 场（`ZOOM_LEAD_SUGGEST_CAP`），先跑一天看结果。
- **看到**：Recordings 页面没 link 的行也显示建议（现在只有 AI Agent 页面有）：「AI 建议：陈先生（+6012-xxx）
  · 原因：客户在 03:12 报了电话号码」，旁边两个按钮：确认 / 不是他。
- **做**：确认 = 直接 link；不是他 = 记下来，不再重复建议同一个人。
- **做好的标准**：开关打开 24 小时后，48 场有 transcript 的 Zoom 全部有「建议」或「找不到」的记录；每场只花一次
  AI 费用。

### 5. 谁做的 Zoom：可以指定顾问

Agent 栏显示的是 Zoom 账号主人。26 场是用共用的 Support 账号开的，看不出是哪位同事做的。

- **看到**：每一行多一栏 Consultant（做 Zoom 的同事），预设等于 Agent；Support 账号的行显示为空，提醒要填。
- **做**：点一下，从同事名单选人。同时在列表加一栏 Assigned Closer（这个客户在 CRM 分配给谁），66 个空的一眼看到。
- **做好的标准**：老板筛 Consultant = Zen 就能看到 Zen 做过的所有 Zoom，不管用哪个账号开。

### 6. 排了却没开的 Zoom

38 场是在系统里排的（Property Match 等），时间过了，Zoom 没有回报结束，也没有 recording，一直挂在 Upcoming。

- **看到**：Zoom Dashboard 上一个「已过时间、没有 recording」的清单。
- **做**：每一场可以标记「有开，没录」或「客户没来」（No-show）。
- **做好的标准**：Upcoming 里没有超过 24 小时以前的 Zoom。

### 附带：Export 按钮

- Recordings 页面右上加 Export（Excel / CSV），栏位和这次的 Excel 一样：日期时间（马来西亚时间）、Consultant、Topic、
  长度、客户名 / 电话 / email、Level 中英文、Assigned Closer、最新 engagement、有没有 transcript、AI 状态。
- 筛选了 Unlinked 再 Export，档案里就只有 Unlinked。这样老板以后要看，自己按一下就好。

## IT 技术备注

这一段给写代码的同事看。

- **列表和 Level 栏**：`RecordingsController@index` 的每一行已经 eager load `lead`；Level 用
  `LeadStages::applyFilter()` 的 SQL twin 算（对当页的 lead id 跑 11 次 `whereIn` + `applyFilter`，或做成一个
  `LeadStages::stagesFor(Collection $leadIds)` helper），**不要**在前端或 controller 抄一份规则。Badge 用
  Leads 列表现成的组件；颜色名来自 `LeadStages::STAGES[..]['color']`。
- **Link status 筛选**：`RecordingsQueryRequest` 加 `link_status` 到 `$filterable`，`filterLinkStatus()`：
  `linked` = `lead_id IS NOT NULL`、`unlinked` = `lead_id IS NULL AND excluded_at IS NULL`、`excluded` =
  `excluded_at IS NOT NULL`。Counts 和其他 dimension 一样在 controller 算，走 `LeadVisibility::applyToZoomMeetings`。
- **排除**：`zoom_meetings` 加 `excluded_at`（datetime, nullable, index）、`excluded_reason`（unsignedInteger，常数在
  `ZoomMeeting::EXCLUDE_REASONS`）、`excluded_by`。写入放 `ZoomMeetingRepository::exclude()` /
  `restore()`，各自 `DB::transaction`。路由 `POST zoom/recordings/{id}/exclude`，权限 `MANAGE_ZOOM`。
- **AI 建议**：全部已在——`app/Console/Commands/SuggestZoomLeads.php`（`zoom:suggest-leads`，Kernel 每小时排程）
  → `App\Jobs\Ai\SuggestZoomMeetingLead` → `ZoomMeetingRepository::recordLeadSuggestion()`；确认 / 拒绝是
  `confirmSuggestedLead()` / `dismissSuggestedLead()`，AI Agent 页面（`ZoomAiAgentController`）已接。排程被
  `config('services.zoom.lead_suggest_enabled')` 挡住，对应 `.env` 的 `ZOOM_LEAD_SUGGEST`（`filter_var` 布尔，
  写 `true` 或 `1` 都可以）。production 要做的：`.env` 加 `ZOOM_LEAD_SUGGEST=true`，`php artisan config:cache`，
  确认 scheduler 在跑（`petav3-scheduler` service），隔天查 `SELECT COUNT(*) FROM zoom_meetings WHERE
  lead_suggested_at IS NOT NULL`。剩下的开发只有一件：Recordings 列表的行 transform 带上
  `suggested_lead`（名字、电话、`suggested_lead_reason`）和确认 / 拒绝两个按钮，调用现成的两个 repository 方法。
  Sweep 的 `whereNull('lead_id')` 条件要加 `whereNull('excluded_at')`（第 2 项加的栏位），排除掉的不要花 AI 费。
- **Consultant**：`zoom_meetings` 加 `consultant_admin_id`（nullable, index），关系 `consultant()` → `Admin`。
  显示 `consultant ?? agent`；Support 账号的 admin id 用 config 列出来，这些行才显示为空。Assigned Closer 直接读
  `lead.assignedAdmin.profile.full_name`，已 eager load 一半，补上就好。
- **没开的 Zoom**：`ZoomMeeting::DISPLAY_PAST` 已经定义了「时间过了还是 Upcoming」的显示逻辑；新加两个状态写入
  `ZoomMeetingRepository`：`markHeld()`（status ENDED，无 recording）和 `markNoShow()`（新常数
  `STATUS_NO_SHOW = 5`）。
- **Export**：照 `docs/modules_handbook/shared/export/readMe.md`：`ExportsResource` trait + `App\Exports\ZoomRecordingsExport`
  （`FromQuery`，`orderBy('zoom_meetings.id')` 做 tiebreaker）+ `<ExportMenu :base-url>`，`GET export` 路由要放在
  `{id}` 之前。和列表共用同一个 query（`visibleRecordingsQuery`），`per_page` 不能传进 export。电话栏保持文字，
  日期转 `Asia/Kuala_Lumpur`（sync 进来的 `start_time` 是 UTC）。
- **文件更新**：改完要更新 `docs/modules_handbook/zoom/`（或相应 readMe）的 Nav 和字段说明。
- **测试**：Link status 三种筛选各一条；Level 栏和 `LeadStages::applyFilter` 对同一批 lead 结果一致；export
  行数等于列表 total；排除后 Unlinked count 减一。

## 验收（老板怎么验）

1. 打开 Recordings，筛 Unlinked，今天应看到 64；把一场 link 给客户，数量变 63。
2. 随便挑一个已 link 的客户，Recordings 上的 Level 和 Leads 列表上他的 Status 一样。
3. 按 Export，下载的 Excel 和这次手工抽的档案栏位一样、行数一样。
4. 用 Support 账号开的 Zoom，可以填 Consultant，之后筛 Consultant = Zen 能看到。
