Files
gochat/docs/requirements/M5-team-and-assignment.md
T
2026-06-04 15:44:48 +08:00

467 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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层)**:
- 无独立 serviceCRUD 直接在 controller 中完成
- `Team#add_members(user_ids)` — 批量添加成员,同时更新账户缓存
- `Team#remove_members(user_ids)` — 批量移除成员,同时更新账户缓存
- `before_validation` 自动将 name 转为 lowercase
- 删除团队时关联的 conversations 的 team_id 被设为 NULLnullify
- **涉及的自动化/规则/事件**
- 团队创建/删除会触发 `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_windowV2
- **涉及的后台 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 | 💎 企业版 |