467 lines
28 KiB
Markdown
467 lines
28 KiB
Markdown
# M5: 团队与分配(Team / Assignment / AutoAssignment)
|
||
|
||
## 模块概述
|
||
|
||
本模块覆盖 Chatwoot 的团队管理、对话分配(人工/自动)、分配策略(Assignment Policy V2)、坐席容量策略(Agent Capacity Policy,企业版)等功能。核心目标是让客服团队可以组织成员、并基于策略自动或手动将对话分配给合适的坐席。
|
||
|
||
---
|
||
|
||
### 功能1:团队管理(Team CRUD)
|
||
|
||
- **功能描述**:在账户级别创建、编辑、删除团队。团队是坐席的逻辑分组,可用于对话分配、权限管理等场景。团队名称在同一账户下唯一。
|
||
- **用户操作流程(UI交互步骤)**:
|
||
1. 进入 Settings → Teams 页面
|
||
2. 点击"Add Team"按钮,填写团队名称(name)和描述(description)
|
||
3. 可选择是否允许自动分配(allow_auto_assign),默认为 true
|
||
4. 创建后可在团队列表中查看、编辑、删除
|
||
- **涉及的API端点 + 请求/响应格式**:
|
||
- `GET /api/v1/accounts/{account_id}/teams` — 获取团队列表
|
||
- `POST /api/v1/accounts/{account_id}/teams` — 创建团队
|
||
- 请求:`{ team: { name: "sales", description: "Sales team", allow_auto_assign: true } }`
|
||
- 响应:团队对象 `{ id, name, description, allow_auto_assign, account_id, is_member }`
|
||
- `GET /api/v1/accounts/{account_id}/teams/{id}` — 获取单个团队
|
||
- `PUT /api/v1/accounts/{account_id}/teams/{id}` — 更新团队
|
||
- `DELETE /api/v1/accounts/{account_id}/teams/{id}` — 删除团队
|
||
- **涉及的数据模型 + 关键字段**:
|
||
- `Team` 模型(表 `teams`):
|
||
- `id` (bigint, PK)
|
||
- `name` (string, not null, 同账户下唯一,自动 downcase)
|
||
- `description` (text)
|
||
- `allow_auto_assign` (boolean, default true) — 是否允许该团队参与自动分配
|
||
- `account_id` (bigint, not null, FK)
|
||
- `created_at`, `updated_at`
|
||
- 关联:`has_many :team_members`, `has_many :members (through :team_members, source: :user)`, `has_many :conversations (dependent: :nullify)`
|
||
- **涉及的业务逻辑(service层)**:
|
||
- 无独立 service,CRUD 直接在 controller 中完成
|
||
- `Team#add_members(user_ids)` — 批量添加成员,同时更新账户缓存
|
||
- `Team#remove_members(user_ids)` — 批量移除成员,同时更新账户缓存
|
||
- `before_validation` 自动将 name 转为 lowercase
|
||
- 删除团队时关联的 conversations 的 team_id 被设为 NULL(nullify)
|
||
- **涉及的自动化/规则/事件**:
|
||
- 团队创建/删除会触发 `AccountCacheRevalidator` 更新缓存
|
||
- **Chatwoot原实现的关键代码文件路径**:
|
||
- `app/models/team.rb`
|
||
- `app/controllers/api/v1/accounts/teams_controller.rb`
|
||
- `app/views/api/v1/accounts/teams/` (index, show, create, update)
|
||
- `app/views/api/v1/models/_team.json.jbuilder`
|
||
- `db/schema.rb` — teams 表
|
||
- **版本标注**:✅ 社区版
|
||
|
||
---
|
||
|
||
### 功能2:团队成员管理(Team Member CRUD)
|
||
|
||
- **功能描述**:为团队添加或移除坐席成员。成员必须属于同一账户。支持批量操作。
|
||
- **用户操作流程(UI交互步骤)**:
|
||
1. 在团队详情页中点击"Add Agents"
|
||
2. 从下拉列表中选择要添加的坐席(多选)
|
||
3. 可通过 update 接口同时添加新成员和移除旧成员
|
||
4. 可批量删除指定成员
|
||
- **涉及的API端点 + 请求/响应格式**:
|
||
- `GET /api/v1/accounts/{account_id}/teams/{team_id}/team_members` — 获取团队成员列表
|
||
- `POST /api/v1/accounts/{account_id}/teams/{team_id}/team_members` — 添加成员
|
||
- 请求:`{ user_ids: [1, 2, 3] }`
|
||
- 响应:成员用户对象数组
|
||
- `PATCH /api/v1/accounts/{account_id}/teams/{team_id}/team_members/update` — 更新成员列表(同时添加+移除)
|
||
- 请求:`{ user_ids: [1, 3, 5] }` — 对比当前成员列表,自动计算添加和移除
|
||
- `DELETE /api/v1/accounts/{account_id}/teams/{team_id}/team_members/destroy` — 移除指定成员
|
||
- 请求:`{ user_ids: [2, 4] }`
|
||
- **涉及的数据模型 + 关键字段**:
|
||
- `TeamMember` 模型(表 `team_members`):
|
||
- `id` (bigint, PK)
|
||
- `team_id` (bigint, not null, FK → teams)
|
||
- `user_id` (bigint, not null, FK → users)
|
||
- `created_at`, `updated_at`
|
||
- 唯一约束:`(team_id, user_id)` — 同一坐席不能在同一团队中重复
|
||
- **涉及的业务逻辑(service层)**:
|
||
- Controller 中校验 user_ids 必须属于当前账户(`validate_member_id_params`)
|
||
- `update` 动作使用 `members_to_be_added_ids` / `members_to_be_removed_ids` 自动计算差集
|
||
- 调用 `Team#add_members` / `Team#remove_members` 执行操作
|
||
- 操作后触发 `AccountCacheRevalidator` 更新缓存
|
||
- **涉及的自动化/规则/事件**:
|
||
- TeamMember 包含 `Audit::TeamMember` 模块用于审计追踪
|
||
- **Chatwoot原实现的关键代码文件路径**:
|
||
- `app/models/team_member.rb`
|
||
- `app/controllers/api/v1/accounts/team_members_controller.rb`
|
||
- `app/views/api/v1/accounts/team_members/` (index, create)
|
||
- `db/schema.rb` — team_members 表
|
||
- **版本标注**:✅ 社区版
|
||
|
||
---
|
||
|
||
### 功能3:对话参与者管理(Conversation Participant)
|
||
|
||
- **功能描述**:对话参与者(ConversationParticipant)是对话中除主分配人(assignee)之外的附加参与者。用于多人协作场景,多个坐席可以同时"参与"一个对话以跟踪其进展。当对话被分配给坐席时,该坐席自动成为参与者。
|
||
- **用户操作流程(UI交互步骤)**:
|
||
1. 在对话详情面板中点击"Add Participants"
|
||
2. 从坐席下拉列表中选择要添加的参与者
|
||
3. 参与者可查看对话并收到通知
|
||
4. 可移除不再需要的参与者
|
||
- **涉及的API端点 + 请求/响应格式**:
|
||
- `GET /api/v1/accounts/{account_id}/conversations/{conversation_id}/participants` — 获取参与者列表
|
||
- `POST /api/v1/accounts/{account_id}/conversations/{conversation_id}/participants` — 添加参与者
|
||
- 请求:`{ user_ids: [1, 2] }`
|
||
- 响应:参与者对象数组
|
||
- `PATCH /api/v1/accounts/{account_id}/conversations/{conversation_id}/participants` — 更新参与者列表
|
||
- 请求:`{ user_ids: [1, 3] }` — 自动计算添加和移除差集
|
||
- `DELETE /api/v1/accounts/{account_id}/conversations/{conversation_id}/participants` — 移除参与者
|
||
- 请求:`{ user_ids: [2] }`
|
||
- **涉及的数据模型 + 关键字段**:
|
||
- `ConversationParticipant` 模型(表 `conversation_participants`):
|
||
- `id` (bigint, PK)
|
||
- `account_id` (bigint, not null)
|
||
- `user_id` (bigint, not null)
|
||
- `conversation_id` (bigint, not null)
|
||
- `created_at`, `updated_at`
|
||
- 唯一约束:`(user_id, conversation_id)` — 同一坐席不重复参与同一对话
|
||
- **涉及的业务逻辑(service层)**:
|
||
- `ensure_inbox_access` 校验:参与者必须是该对话所在 inbox 的 assignable_agents
|
||
- `ensure_account_id` 校验:account_id 自动从 conversation 继承
|
||
- `ParticipationListener#assignee_changed`:当对话 assignee 变化时,自动将新 assignee 添加为参与者
|
||
- `Messages::MentionService`:当坐席被 @提及 时自动添加为参与者
|
||
- `Messages::NewMessageNotificationService`:向所有参与者推送通知
|
||
- **涉及的自动化/规则/事件**:
|
||
- 事件 `ASSIGNEE_CHANGED` → `ParticipationListener` 自动创建参与者记录
|
||
- 处理并发冲突:`rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid`
|
||
- **Chatwoot原实现的关键代码文件路径**:
|
||
- `app/models/conversation_participant.rb`
|
||
- `app/controllers/api/v1/accounts/conversations/participants_controller.rb`
|
||
- `app/listeners/participation_listener.rb`
|
||
- `app/services/messages/mention_service.rb`
|
||
- `app/services/messages/new_message_notification_service.rb`
|
||
- `db/schema.rb` — conversation_participants 表
|
||
- `config/routes.rb` — `resource :participants, only: [:show, :create, :update, :destroy]`
|
||
- **版本标注**:✅ 社区版
|
||
|
||
---
|
||
|
||
### 功能4:分配策略管理(Assignment Policy V2 CRUD)
|
||
|
||
- **功能描述**:分配策略(Assignment Policy)定义了对话自动分配的规则,包括分配顺序(round_robin/balanced)、对话优先级、公平分配限制等。一个策略可关联多个 Inbox,每个 Inbox 只能关联一个策略。这是 V2 版本的自动分配机制。
|
||
- **用户操作流程(UI交互步骤)**:
|
||
1. 进入 Settings → Assignment Policies 页面(需启用 assignment_v2 feature flag)
|
||
2. 创建策略:填写名称、描述、分配顺序、对话优先级、公平分配限制/窗口等
|
||
3. 将策略关联到 Inbox
|
||
4. 可启用/禁用策略
|
||
- **涉及的API端点 + 请求/响应格式**:
|
||
- `GET /api/v1/accounts/{account_id}/assignment_policies` — 获取策略列表
|
||
- `POST /api/v1/accounts/{account_id}/assignment_policies` — 创建策略
|
||
- 请求:`{ assignment_policy: { name: "Default", description: "...", assignment_order: "round_robin", conversation_priority: "earliest_created", fair_distribution_limit: 5, fair_distribution_window: 300, enabled: true } }`
|
||
- 响应:策略对象 `{ id, name, description, assignment_order, conversation_priority, fair_distribution_limit, fair_distribution_window, enabled, assigned_inbox_count, created_at, updated_at }`
|
||
- `GET /api/v1/accounts/{account_id}/assignment_policies/{id}` — 获取单个策略
|
||
- `PUT /api/v1/accounts/{account_id}/assignment_policies/{id}` — 更新策略
|
||
- `DELETE /api/v1/accounts/{account_id}/assignment_policies/{id}` — 删除策略
|
||
- `GET /api/v1/accounts/{account_id}/assignment_policies/{id}/inboxes` — 获取策略关联的 Inbox 列表
|
||
- `POST /api/v1/accounts/{account_id}/assignment_policies/{id}/inboxes` — 将策略关联到 Inbox
|
||
- `DELETE /api/v1/accounts/{account_id}/assignment_policies/{id}/inboxes/{inbox_id}` — 解除 Inbox 关联
|
||
- **涉及的数据模型 + 关键字段**:
|
||
- `AssignmentPolicy` 模型(表 `assignment_policies`):
|
||
- `id` (bigint, PK)
|
||
- `account_id` (bigint, not null)
|
||
- `name` (string 255, not null, 同账户下唯一)
|
||
- `description` (text)
|
||
- `assignment_order` (integer, default 0) — enum: `round_robin: 0`, 企业版额外 `balanced: 1`
|
||
- `conversation_priority` (integer, default 0) — enum: `earliest_created: 0`, `longest_waiting: 1`
|
||
- `fair_distribution_limit` (integer, default 100, >0) — 在窗口内给单个坐席最多分配多少对话
|
||
- `fair_distribution_window` (integer, default 3600, >0) — 公平分配窗口时间(秒)
|
||
- `enabled` (boolean, default true)
|
||
- `created_at`, `updated_at`
|
||
- `InboxAssignmentPolicy` 模型(表 `inbox_assignment_policies`):
|
||
- `id` (bigint, PK)
|
||
- `inbox_id` (bigint, not null, unique) — 每个 Inbox 只能关联一个策略
|
||
- `assignment_policy_id` (bigint, not null)
|
||
- `created_at`, `updated_at`
|
||
- 关联:`AssignmentPolicy has_many :inbox_assignment_policies`, `has_many :inboxes (through :inbox_assignment_policies)`
|
||
- **涉及的业务逻辑(service层)**:
|
||
- CRUD 在 controller 中完成
|
||
- `Inboxes::AssignmentPoliciesController#create` 会先移除旧策略再关联新的(一对一关系)
|
||
- 企业版差异:`Enterprise::Concerns::AssignmentPolicy` 增加 `balanced` assignment_order enum
|
||
- **涉及的自动化/规则/事件**:
|
||
- 无独立事件触发
|
||
- **Chatwoot原实现的关键代码文件路径**:
|
||
- `app/models/assignment_policy.rb`
|
||
- `app/models/inbox_assignment_policy.rb`
|
||
- `app/controllers/api/v1/accounts/assignment_policies_controller.rb`
|
||
- `app/controllers/api/v1/accounts/assignment_policies/inboxes_controller.rb`
|
||
- `app/controllers/api/v1/accounts/inboxes/assignment_policies_controller.rb`
|
||
- `app/views/api/v1/accounts/assignment_policies/` (index, show, create, update, _partial)
|
||
- `app/views/api/v1/accounts/assignment_policies/inboxes/` (index, create)
|
||
- `enterprise/app/models/enterprise/concerns/assignment_policy.rb`
|
||
- `db/schema.rb` — assignment_policies, inbox_assignment_policies 表
|
||
- **版本标注**:✅ 社区版(基础 round_robin);💎 企业版(额外 balanced 分配顺序)
|
||
|
||
---
|
||
|
||
### 功能5:Inbox 级别分配策略关联
|
||
|
||
- **功能描述**:为 Inbox 直接设置其关联的分配策略。每个 Inbox 只能关联一个 AssignmentPolicy。这是一个便捷的 Inbox → AssignmentPolicy 一对一管理入口。
|
||
- **用户操作流程(UI交互步骤)**:
|
||
1. 在 Inbox 设置页面中选择"Assignment Policy"
|
||
2. 选择一个已有的分配策略进行关联
|
||
3. 可查看当前关联的策略
|
||
4. 可解除关联
|
||
- **涉及的API端点 + 请求/响应格式**:
|
||
- `GET /api/v1/accounts/{account_id}/inboxes/{inbox_id}/assignment_policy` — 获取 Inbox 当前关联的策略
|
||
- `POST /api/v1/accounts/{account_id}/inboxes/{inbox_id}/assignment_policy` — 关联策略
|
||
- 请求:`{ assignment_policy_id: 1 }`
|
||
- 响应:AssignmentPolicy 对象
|
||
- `DELETE /api/v1/accounts/{account_id}/inboxes/{inbox_id}/assignment_policy` — 解除关联
|
||
- **涉及的数据模型 + 关键字段**:
|
||
- 同"功能4"中的 `InboxAssignmentPolicy` 和 `AssignmentPolicy`
|
||
- Inbox 模型关联:`has_one :inbox_assignment_policy`, `has_one :assignment_policy, through: :inbox_assignment_policy`
|
||
- **涉及的业务逻辑(service层)**:
|
||
- `Inboxes::AssignmentPoliciesController#create`:
|
||
- 先移除旧的 `inbox_assignment_policy`(如果有)
|
||
- 再创建新的关联
|
||
- `validate_assignment_policy`:确保 Inbox 已关联策略才可 show/destroy
|
||
- **Chatwoot原实现的关键代码文件路径**:
|
||
- `app/controllers/api/v1/accounts/inboxes/assignment_policies_controller.rb`
|
||
- `app/models/inbox_assignment_policy.rb`
|
||
- `app/models/inbox.rb` — `has_one :inbox_assignment_policy`, `has_one :assignment_policy`
|
||
- **版本标注**:✅ 社区版
|
||
|
||
---
|
||
|
||
### 功能6:自动分配引擎(Auto Assignment Engine)
|
||
|
||
- **功能描述**:当新对话创建或对话变为 open/unassigned 状态时,系统根据 Inbox 的分配策略自动将对话分配给可用坐席。核心分配逻辑包括:筛选有容量的在线坐席、团队过滤、公平分配限制、轮询选择。
|
||
- **用户操作流程(UI交互步骤)**:
|
||
1. 管理员在 Inbox 设置中启用自动分配(`enable_auto_assignment`)
|
||
2. 如启用 V2(`assignment_v2` feature flag),需要先为 Inbox 关联 AssignmentPolicy
|
||
3. 对话进入后系统自动分配给坐席
|
||
4. 可在对话详情中看到被自动分配的坐席
|
||
- **涉及的API端点 + 请求/响应格式**:
|
||
- 无直接 API 端点,由后台 Job 自动执行
|
||
- **涉及的数据模型 + 关键字段**:
|
||
- `Inbox` 模型:
|
||
- `enable_auto_assignment` (boolean, default true)
|
||
- `auto_assignment_config` (jsonb) — V1 版配置
|
||
- `auto_assignment_v2_enabled?` — 检查 `account.feature_enabled?('assignment_v2')`
|
||
- `Conversation` 模型:
|
||
- `assignee_id` (integer) — 主分配坐席
|
||
- `team_id` (bigint) — 分配的团队
|
||
- `status` (integer) — 对话状态
|
||
- Redis 数据结构:
|
||
- `round_robin_key` — Redis List,存储 Inbox 下坐席的轮询顺序
|
||
- `assignment_key_pattern` — Redis Key,用于公平分配限流追踪
|
||
- **涉及的业务逻辑(service层)**:
|
||
- **`AutoAssignment::AssignmentService`**(核心调度器):
|
||
- `perform_bulk_assignment(limit: 100)` — 批量处理未分配对话
|
||
- `unassigned_conversations(limit)` — 获取 open+unassigned 对话,按策略排序
|
||
- `find_available_agent(conversation)` — 筛选可用坐席:
|
||
1. 按团队过滤(如果对话有 team_id 且团队 allow_auto_assign=true)
|
||
2. 按 RateLimiter 过滤(超出公平分配限制的坐席被排除)
|
||
3. RoundRobinSelector 从剩余坐席中选择下一个
|
||
- `assign_conversation(conversation, agent)` — 执行分配,更新 conversation.assignee
|
||
- **`AutoAssignment::InboxRoundRobinService`**(轮询队列管理):
|
||
- Redis List 实现轮询队列
|
||
- `add_agent_to_queue(user_id)` / `remove_agent_from_queue(user_id)` — 坐席变更时维护队列
|
||
- `available_agent(allowed_agent_ids)` — 从队列中选取下一个坐席,pop+push 实现轮询
|
||
- `validate_queue?` — 验证队列与实际 inbox_members 一致,不一致时 reset_queue
|
||
- `reset_queue` — 清空并重建队列
|
||
- **`AutoAssignment::RoundRobinSelector`**:
|
||
- 代理层,委托 InboxRoundRobinService 执行选择
|
||
- **`AutoAssignment::AgentAssignmentService`**:
|
||
- 用于已有允许坐席列表的场景(如手动指定坐席池)
|
||
- 只在在线坐席中轮询分配
|
||
- 使用 `OnlineStatusTracker` 获取在线坐席列表
|
||
- **`AutoAssignment::RateLimiter`**:
|
||
- `within_limit?` — 检查坐席在时间窗口内的分配数是否低于限制
|
||
- `track_assignment(conversation)` — 记录分配事件到 Redis
|
||
- 默认限制:5 次/5分钟(V1),策略配置:fair_distribution_limit/fair_distribution_window(V2)
|
||
- **涉及的后台 Job**:
|
||
- `AutoAssignment::AssignmentJob`:
|
||
- 批量分配 Job,使用 Redis `in_flight` marker 防止同 Inbox 并发
|
||
- `enqueue_for_inbox(inbox_id)` — 带 NX 锁的 enqueue,确保每个 Inbox 同时只有一个 Job
|
||
- TTL 5分钟自动释放
|
||
- `AutoAssignment::PeriodicAssignmentJob`:
|
||
- 定时调度 Job(Cron),遍历所有启用 assignment_v2 的账户和 Inbox
|
||
- 对每个符合条件的 Inbox enqueue AssignmentJob
|
||
- **涉及的自动化/规则/事件**:
|
||
- 事件 `CONVERSATION_CREATED` → 触发自动分配检查
|
||
- Inbox member 变更 → 更新 round_robin 队列
|
||
- 对话状态变更 → 重新触发分配
|
||
- **Chatwoot原实现的关键代码文件路径**:
|
||
- `app/services/auto_assignment/assignment_service.rb`
|
||
- `app/services/auto_assignment/agent_assignment_service.rb`
|
||
- `app/services/auto_assignment/round_robin_selector.rb`
|
||
- `app/services/auto_assignment/inbox_round_robin_service.rb`
|
||
- `app/services/auto_assignment/rate_limiter.rb`
|
||
- `app/jobs/auto_assignment/assignment_job.rb`
|
||
- `app/jobs/auto_assignment/periodic_assignment_job.rb`
|
||
- `app/models/inbox.rb` — enable_auto_assignment, auto_assignment_config, auto_assignment_v2_enabled?
|
||
- **版本标注**:✅ 社区版(Round Robin 分配);💎 企业版(增加 Balanced 分配 + 容量过滤)
|
||
|
||
---
|
||
|
||
### 功能7:企业版 — 坐席容量策略(Agent Capacity Policy)
|
||
|
||
- **功能描述**:企业版功能,定义坐席的对话容量上限策略。可以为不同坐席分配不同的容量策略,每个策略可以针对不同 Inbox 设置不同的对话数量上限(inbox_capacity_limit)。当坐席达到容量上限时,自动分配将跳过该坐席。
|
||
- **用户操作流程(UI交互步骤)**:
|
||
1. 进入 Settings → Agent Capacity Policies 页面(企业版专属)
|
||
2. 创建容量策略:填写名称、描述、排除规则(exclusion_rules)
|
||
3. 为策略添加坐席用户
|
||
4. 为策略配置每个 Inbox 的对话上限(inbox_limits)
|
||
- **涉及的API端点 + 请求/响应格式**:
|
||
- `GET /api/v1/accounts/{account_id}/agent_capacity_policies` — 列表
|
||
- `POST /api/v1/accounts/{account_id}/agent_capacity_policies` — 创建
|
||
- 请求:`{ agent_capacity_policy: { name: "Standard", description: "...", exclusion_rules: { exclude_older_than_hours: 24, excluded_labels: ["spam"] } } }`
|
||
- 响应:AgentCapacityPolicy 对象 `{ id, name, description, exclusion_rules, account_id }`
|
||
- `GET /api/v1/accounts/{account_id}/agent_capacity_policies/{id}` — 详情
|
||
- `PUT /api/v1/accounts/{account_id}/agent_capacity_policies/{id}` — 更新
|
||
- `DELETE /api/v1/accounts/{account_id}/agent_capacity_policies/{id}` — 删除
|
||
- `GET /api/v1/accounts/{account_id}/agent_capacity_policies/{id}/users` — 获取策略下的坐席列表
|
||
- `POST /api/v1/accounts/{account_id}/agent_capacity_policies/{id}/users` — 为坐席分配此策略
|
||
- 请求:`{ user_id: 1 }`
|
||
- `DELETE /api/v1/accounts/{account_id}/agent_capacity_policies/{id}/users/{user_id}` — 解除坐席的策略关联
|
||
- `POST /api/v1/accounts/{account_id}/agent_capacity_policies/{id}/inbox_limits` — 为策略添加 Inbox 容量上限
|
||
- 请求:`{ inbox_id: 1, conversation_limit: 10 }`
|
||
- `PUT /api/v1/accounts/{account_id}/agent_capacity_policies/{id}/inbox_limits/{id}` — 更新 Inbox 容量上限
|
||
- `DELETE /api/v1/accounts/{account_id}/agent_capacity_policies/{id}/inbox_limits/{id}` — 删除 Inbox 容量上限
|
||
- **涉及的数据模型 + 关键字段**:
|
||
- `AgentCapacityPolicy` 模型(表 `agent_capacity_policies`):
|
||
- `id` (bigint, PK)
|
||
- `account_id` (bigint, not null)
|
||
- `name` (string 255, not null)
|
||
- `description` (text)
|
||
- `exclusion_rules` (jsonb, default {}, not null) — 排除规则:
|
||
- `exclude_older_than_hours` — 排超过多少小时的老对话不计入容量
|
||
- `excluded_labels` — 带这些标签的对话不计入容量
|
||
- `created_at`, `updated_at`
|
||
- `InboxCapacityLimit` 模型(表 `inbox_capacity_limits`):
|
||
- `id` (bigint, PK)
|
||
- `agent_capacity_policy_id` (bigint, not null)
|
||
- `inbox_id` (bigint, not null)
|
||
- `conversation_limit` (integer, not null, ≥0) — 该 Inbox 下的对话容量上限
|
||
- 唯一约束:`(agent_capacity_policy_id, inbox_id)`
|
||
- 关联:
|
||
- `AgentCapacityPolicy has_many :inbox_capacity_limits`, `has_many :inboxes (through)`
|
||
- `AgentCapacityPolicy has_many :account_users (dependent: :nullify)` — AccountUser 的 agent_capacity_policy_id 字段
|
||
- **涉及的业务逻辑(service层)**:
|
||
- **`Enterprise::AutoAssignment::CapacityService`**:
|
||
- `agent_has_capacity?(user, inbox)` — 判断坐席在指定 Inbox 下是否有容量:
|
||
1. 获取坐席的 `AccountUser.agent_capacity_policy`
|
||
2. 如无策略或无该 Inbox 的 limit → 返回 true(无限容量)
|
||
3. 计算 `user.assigned_conversations.where(inbox:, status: :open).count`
|
||
4. 比较 current_count < conversation_limit
|
||
- **`Enterprise::AutoAssignment::AssignmentService`**(扩展社区版):
|
||
- `filter_agents_by_capacity(agents)` — 过滤掉无容量的坐席
|
||
- `capacity_filtering_enabled?` — 需要 `advanced_assignment` feature 且有坐席被分配了策略
|
||
- **`Enterprise::InboxAgentAvailability`**(扩展 Inbox 模型):
|
||
- `member_ids_with_assignment_capacity` — 返回有容量的坐席 ID 列表(用于 V1 自动分配)
|
||
- **涉及的自动化/规则/事件**:
|
||
- 无独立事件,自动分配流程中实时检查容量
|
||
- **Chatwoot原实现的关键代码文件路径**:
|
||
- `enterprise/app/models/agent_capacity_policy.rb`
|
||
- `enterprise/app/models/inbox_capacity_limit.rb`
|
||
- `enterprise/app/controllers/api/v1/accounts/agent_capacity_policies_controller.rb`
|
||
- `enterprise/app/controllers/api/v1/accounts/agent_capacity_policies/users_controller.rb`
|
||
- `enterprise/app/controllers/api/v1/accounts/agent_capacity_policies/inbox_limits_controller.rb`
|
||
- `enterprise/app/services/enterprise/auto_assignment/capacity_service.rb`
|
||
- `enterprise/app/services/enterprise/auto_assignment/assignment_service.rb`
|
||
- `enterprise/app/models/enterprise/inbox_agent_availability.rb`
|
||
- `enterprise/app/views/api/v1/accounts/agent_capacity_policies/` (index, show, create, update)
|
||
- `enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/` (create, update)
|
||
- `enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/` (index, create)
|
||
- `db/schema.rb` — agent_capacity_policies, inbox_capacity_limits 表
|
||
- **版本标注**:💎 企业版(Business/Enterprise 计划专属,需 `advanced_assignment` feature flag)
|
||
|
||
---
|
||
|
||
### 功能8:企业版 — 均衡分配选择器(Balanced Selector)
|
||
|
||
- **功能描述**:企业版的高级分配算法。与 Round Robin(轮询)不同,Balanced Selector 选择当前分配对话数最少的坐席,实现真正的负载均衡。
|
||
- **用户操作流程(UI交互步骤)**:
|
||
1. 在 AssignmentPolicy 中设置 `assignment_order` 为 `balanced`
|
||
2. 系统自动使用 Balanced Selector 代替 Round Robin
|
||
- **涉及的数据模型 + 关键字段**:
|
||
- `AssignmentPolicy.assignment_order = "balanced"` (enum value 1)
|
||
- **涉及的业务逻辑(service层)**:
|
||
- **`Enterprise::AutoAssignment::BalancedSelector`**:
|
||
- `select_agent(available_agents)` — 选择当前对话数最少的坐席
|
||
- `fetch_assignment_counts(users)` — 查询 `inbox.conversations.open.where(assignee_id: user_ids).group(:assignee_id).count`
|
||
- 使用 `min_by { |user| assignment_counts[user.id] || 0 }` 选择负载最低的坐席
|
||
- **`Enterprise::AutoAssignment::AssignmentService#find_available_agent`**:
|
||
- 判断:如果 `policy.balanced? && account.feature_enabled?('advanced_assignment')` → 使用 BalancedSelector
|
||
- 否则使用 RoundRobinSelector
|
||
- **涉及的自动化/规则/事件**:
|
||
- 无独立事件
|
||
- **Chatwoot原实现的关键代码文件路径**:
|
||
- `enterprise/app/services/enterprise/auto_assignment/balanced_selector.rb`
|
||
- `enterprise/app/services/enterprise/auto_assignment/assignment_service.rb`
|
||
- `enterprise/app/models/enterprise/concerns/assignment_policy.rb`
|
||
- **版本标注**:💎 企业版
|
||
|
||
---
|
||
|
||
### 功能9:手动分配对话(Assign/Unassign Agent/Team)
|
||
|
||
- **功能描述**:坐席或管理员可以手动将对话分配给指定坐席或团队,或取消分配。这是最基础的对话分配操作。
|
||
- **用户操作流程(UI交互步骤)**:
|
||
1. 在对话详情面板点击"Assign Agent"或"Assign Team"
|
||
2. 从下拉列表中选择坐席或团队
|
||
3. 可点击"Unassign"取消分配
|
||
4. 对话面板实时更新显示分配信息
|
||
- **涉及的API端点 + 请求/响应格式**:
|
||
- 对话更新 API 中包含 `assignee_id` 和 `team_id` 字段:
|
||
- `POST /api/v1/accounts/{account_id}/conversations/{id}/assignments` — 分配坐席
|
||
- 请求:`{ assignee_id: 1 }` 或 `{ team_id: 2 }`
|
||
- 对话 update API 中也可以修改 assignee_id/team_id
|
||
- **涉及的数据模型 + 关键字段**:
|
||
- `Conversation` 模型:
|
||
- `assignee_id` (integer) — 主分配坐席 ID
|
||
- `team_id` (bigint) — 分配的团队 ID
|
||
- `assignee_agent_bot_id` (bigint) — 分配的 Bot ID(与坐席互斥)
|
||
- 校验:`reset_agent_bot_when_assignee_present` — 分配坐席时自动清除 bot 分配
|
||
- **涉及的业务逻辑(service层)**:
|
||
- 对话 model 中 `after_update_commit :dispatcher_dispatch(CONVERSATION_UPDATED, previous_changes)`
|
||
- `dispatcher_dispatch` 触发 `CONVERSATION_UPDATED` 事件 → `AutomationRuleListener` 可能触发自动化规则
|
||
- `after_update_commit :notify_assignee_changed` — 通知被分配的坐席
|
||
- `self_assign?` 检查 — 如果坐席自己接管对话,不发通知
|
||
- **涉及的自动化/规则/事件**:
|
||
- 事件 `CONVERSATION_UPDATED` → 触发自动化规则检查
|
||
- 事件 `ASSIGNEE_CHANGED` → `ParticipationListener` 自动添加参与者
|
||
- **Chatwoot原实现的关键代码文件路径**:
|
||
- `app/models/conversation.rb` — assignee_id, team_id, after_update_commit hooks
|
||
- `app/controllers/api/v1/accounts/conversations/` — assignments 相关
|
||
- `app/listeners/participation_listener.rb`
|
||
- `app/listeners/automation_rule_listener.rb`
|
||
- **版本标注**:✅ 社区版
|
||
|
||
---
|
||
|
||
## 模块间关系图
|
||
|
||
```
|
||
Account
|
||
├── Teams → TeamMembers → Users
|
||
├── AssignmentPolicies → InboxAssignmentPolicies → Inboxes
|
||
└── [企业版] AgentCapacityPolicies → AccountUsers + InboxCapacityLimits → Inboxes
|
||
|
||
Conversation
|
||
├── assignee_id → User (主分配坐席)
|
||
├── team_id → Team (分配团队)
|
||
├── ConversationParticipants → Users (附加参与者)
|
||
└── Inbox → AssignmentPolicy → AutoAssignment Engine
|
||
|
||
Auto Assignment Engine (社区版 Round Robin)
|
||
├── AssignmentService → 批量分配调度
|
||
├── InboxRoundRobinService → Redis 轮询队列
|
||
├── RoundRobinSelector → 坐席选择
|
||
├── AgentAssignmentService → 指定坐席池轮询
|
||
├── RateLimiter → 公平分配限流
|
||
├── AssignmentJob → 后台 Job (带 in-flight lock)
|
||
└── PeriodicAssignmentJob → 定时触发
|
||
|
||
Auto Assignment Engine (企业版扩展)
|
||
├── BalancedSelector → 负载均衡选择
|
||
├── CapacityService → 容量检查
|
||
└── InboxAgentAvailability → 容量过滤后的坐席列表
|
||
```
|
||
|
||
## 关键 Feature Flags
|
||
|
||
| Feature Flag | 影响范围 | 版本 |
|
||
|---|---|---|
|
||
| `assignment_v2` | 启用 Assignment Policy V2 + PeriodicAssignmentJob | 社区版(需手动启用) |
|
||
| `advanced_assignment` | 启用 Balanced Selector + Agent Capacity Policy | 💎 企业版 | |