Lead Pool – Giỏ Khách Hàng
I. Tổng quan
Tên tính năng: Lead Pool (Giỏ Khách Hàng) Module: uLead / CRM Sales Version: v1.0 | Ngày soạn: 2026-05-19
1. Vấn đề
- Cherry-picking: Nhân viên sale tự vào danh sách, chọn tay những khách “ngon” (dễ chốt, gần nhà, lớn deal), bỏ qua những khách khó – gây bất công và lãng phí data.
- Ôm khách không xử lý: Nhân viên nhận hàng chục/hàng trăm khách nhưng chỉ gọi một phần, số còn lại bị bỏ bê, “chết nguội” trong hệ thống.
- Thiếu cơ chế kiểm soát tiến độ: Manager không biết nhân viên đang “stuck” ở khách nào, không thể can thiệp kịp thời.
- Không có rào cản chất lượng: Nhân viên có thể liên tục lấy thêm khách mà không cần chứng minh đã xử lý xong khách cũ.
2. Giải pháp
Xây dựng cơ chế Giỏ Khách Hàng (Lead Pool) – một hàng đợi khách hàng trung tâm mà nhân viên sale tự chọn lấy theo điều kiện do manager cấu hình, nhưng bị giới hạn số lượng và phải hoàn thành hành động bắt buộc (Action Gate) trước khi được lấy thêm.
Nguyên lý hoạt động:
[Kho data / Pool trung tâm]
│
▼
[Nhân viên lọc & lấy tối đa X khách vào giỏ cá nhân]
│
▼
[Xử lý: Gọi điện / Ghi chú / Cập nhật trạng thái]
│
▼
[Hoàn thành Action Gate → Mở khoá lấy thêm]
3. Đối tượng
| Role | Quyền |
|---|---|
| Admin / Trưởng nhóm (Manager) | Tạo & cấu hình Lead Pool (bộ lọc, giới hạn số lượng, Action Gate) |
| Nhân viên sale (Agent) | Xem Pool, lấy khách vào giỏ cá nhân, xử lý và hoàn thành Action Gate |
| Nhân viên xem (Viewer) | Chỉ xem báo cáo, không thao tác |
4. Tầm nhìn / Insight (Roadmap tương lai)
- v1: Lấy khách thủ công theo điều kiện + Action Gate đơn giản (ghi chú / đổi trạng thái).
- v2: Tự động phân phối khách theo Round Robin hoặc Lead Score khi nhân viên mở khoá.
- v3: AI gợi ý “khách nên lấy tiếp theo” dựa trên lịch sử chốt sale và năng lực nhân viên.
- v4: Tích hợp gamification – nhân viên kiếm điểm khi xử lý nhanh, leaderboard theo team.
II. Yêu cầu chức năng
1. Danh sách tính năng
- [F1] Tạo & cấu hình Lead Pool (Manager)
- [F2] Xem Lead Pool (Agent)
- [F3] Lấy khách vào Giỏ cá nhân (Agent)
- [F4] Xem Giỏ cá nhân (Agent)
- [F5] Cơ chế Action Gate – khoá / mở khoá lấy thêm (System)
- [F6] Trả khách về Pool (Agent / System tự động)
- [F7] Báo cáo & theo dõi (Manager)
2. Đặc tả chi tiết
[F1] Tạo & Cấu hình Lead Pool
User story: Là một Manager, tôi muốn tạo một "Giỏ khách hàng" với bộ lọc và quy tắc riêng, để nhân viên sale chỉ thấy đúng tập khách phù hợp với chiến dịch.
| Tên Use Case | [UC-01] Tạo Lead Pool |
|---|---|
| Actor | Manager, Admin |
| Pre-condition | Đã đăng nhập, có quyền quản lý Lead Pool |
| Main Flow | 1. Manager vào Cài đặt → Lead Pool → Tạo mới. 2. Điền tên Pool. 3. Cấu hình Bộ lọc khách hàng (xem mục Bộ lọc bên dưới). 4. Cấu hình Giới hạn lấy (xem mục Giới hạn bên dưới). 5. Cấu hình Action Gate (xem mục Action Gate bên dưới). 6. Gán Pool cho nhân viên / nhóm cụ thể (hoặc tất cả). 7. Bấm Lưu → Pool được kích hoạt. |
| Exception | - Thiếu bộ lọc: Hệ thống báo lỗi “Vui lòng chọn ít nhất 1 điều kiện lọc”. - Giới hạn lấy = 0: Không cho lưu. |
| Post-condition | Pool xuất hiện trong danh sách, nhân viên được gán có thể thấy Pool này. |
Cấu hình Bộ lọc khách hàng:
| Trường lọc | Loại điều kiện | Ví dụ |
|---|---|---|
| Nguồn dữ liệu | Danh sách / là / không là | Facebook Ads, Zalo |
| Trạng thái | Là / không là | Mới, Chưa liên lạc |
| Điểm Lead Score | ≥ / ≤ / trong khoảng | ≥ 70 |
| Tỉnh / Thành phố | Trong danh sách | HCM, HN |
| Ngành nghề | Trong danh sách | Bất động sản |
| Ngày tạo | Trong khoảng ngày | 7 ngày gần nhất |
| Sản phẩm quan tâm | Có chứa | Gói Enterprise |
| Nhân viên phụ trách | Không có (unassigned) | (mặc định với Pool) |
Cấu hình Giới hạn lấy:
| Tham số | Mô tả | Giá trị mẫu |
|---|---|---|
max_per_claim |
Số khách tối đa nhân viên được lấy trong 1 lần bấm “Lấy khách” | 5, 10, 20… |
max_capacity |
Tổng số khách tối đa trong Giỏ cá nhân cùng lúc | 30, 50… |
pick_mode |
Nhân viên tự chọn hay hệ thống tự phân | manual / auto |
Cấu hình Action Gate (Điều kiện mở khoá lấy thêm):
| Loại Action Gate | Mô tả |
|---|---|
log_call |
Phải có ít nhất X khách được ghi nhận cuộc gọi |
update_status |
Phải cập nhật trạng thái của X% khách đang giữ |
create_task |
Phải tạo task chăm sóc cho khách mới lấy |
close_lead |
Phải đóng (chốt hoặc loại) ít nhất X khách trước khi lấy thêm |
combined |
Kết hợp nhiều điều kiện (AND/OR) |
[F2 + F3] Xem Pool & Lấy khách vào Giỏ
User story: Là một Nhân viên sale, tôi muốn xem danh sách Pool và tự chọn lấy khách vào Giỏ của mình (tối đa X khách), để chủ động theo đuổi những khách tiềm năng nhất.
| Tên Use Case | [UC-02] Lấy khách vào Giỏ cá nhân |
|---|---|
| Actor | Nhân viên sale |
| Pre-condition | Đã đăng nhập, được gán vào ít nhất 1 Pool. Giỏ cá nhân còn chỗ trống (< max_capacity). Action Gate hiện tại đã được thoả mãn. |
| Main Flow | 1. Nhân viên vào Lead Pool → [Tên Pool]. 2. Hệ thống hiển thị danh sách khách trong Pool (áp dụng bộ lọc Manager đã cấu hình). 3. Nhân viên có thể lọc thêm trong phạm vi Pool (ví dụ: lọc theo tỉnh, sản phẩm). 4. Nhân viên tích chọn khách muốn lấy (tối đa max_per_claim).5. Bấm “Lấy vào Giỏ”. 6. Hệ thống kiểm tra điều kiện → Thêm khách vào Giỏ cá nhân → Loại khách đó khỏi Pool (khách chỉ thuộc 1 giỏ tại 1 thời điểm). |
| Exception | - Chọn vượt max_per_claim: Hệ thống báo lỗi, chặn lấy.- Giỏ đã đầy ( max_capacity): Nút “Lấy vào Giỏ” bị disabled, tooltip: “Giỏ của bạn đã đạt giới hạn. Hãy hoàn thành yêu cầu để lấy thêm.”- Action Gate chưa thoả mãn: Nút “Lấy vào Giỏ” bị locked, hiển thị banner: “Bạn cần [hoàn thành hành động X] trước khi lấy thêm khách.” |
| Post-condition | Khách được gán cho nhân viên, có nhật ký “Được thêm vào Giỏ bởi [tên NV]” tại thời điểm T. |
Màn hình Lead Pool (Agent view) – mô tả UI:
┌─────────────────────────────────────────────────────────┐
│ 🎯 Pool: Khách HCM chưa liên lạc - Gói Pro │
│ 📊 Tổng trong pool: 247 khách | Giỏ của bạn: 8/30 │
│ │
│ ⚠️ Action Gate: Bạn cần ghi nhận cuộc gọi cho │
│ 5 khách nữa trước khi lấy thêm. (Còn 3/5) │
│ │
│ [Bộ lọc nhanh: Tỉnh ▾] [Sản phẩm ▾] [Lead Score ▾] │
│ ───────────────────────────────────────────────────── │
│ ☐ Nguyễn Văn A | HCM | Score: 85 | Mới │
│ ☐ Trần Thị B | HCM | Score: 79 | Mới │
│ ☐ Lê Văn C | HCM | Score: 72 | Mới │
│ ... │
│ │
│ Đã chọn: 0/10 [Lấy vào Giỏ] (bị khoá) │
└─────────────────────────────────────────────────────────┘
[F4] Xem & Quản lý Giỏ cá nhân
User story: Là một Nhân viên sale, tôi muốn xem tất cả khách hàng đang trong Giỏ của mình cùng với trạng thái xử lý, để không bỏ sót ai.
- Nhân viên vào Giỏ của tôi → thấy danh sách khách đã nhận.
- Mỗi khách hiển thị: Tên, trạng thái, ngày lấy, số ngày trong giỏ, hành động cần làm.
- Khách bị highlight đỏ nếu quá hạn xử lý (
overdue_thresholddo Manager cấu hình, ví dụ: > 2 ngày chưa gọi). - Thanh tiến độ Action Gate hiển thị ngay đầu trang:
[═══════░░░] 7/10 hành động hoàn thành.
[F5] Cơ chế Action Gate – Logic khoá/mở khoá
Business rules:
Khi nhân viên bấm "Lấy vào Giỏ":
1. Kiểm tra max_capacity:
IF (giỏ_hiện_tại + số_muốn_lấy) > max_capacity → BLOCK
2. Kiểm tra max_per_claim:
IF số_muốn_lấy > max_per_claim → BLOCK
3. Kiểm tra Action Gate:
IF action_gate.type == "log_call":
count = số khách trong giỏ ĐÃ có ≥ 1 cuộc gọi được ghi nhận
IF count < action_gate.threshold → BLOCK
IF action_gate.type == "close_lead":
count = số khách ĐÃ đóng (chốt/loại) kể từ lần lấy cuối
IF count < action_gate.threshold → BLOCK
IF action_gate.type == "combined":
Áp dụng logic AND/OR tuỳ cấu hình
4. Tất cả PASS → Cho phép lấy, ghi log, cập nhật giỏ
[F6] Trả khách về Pool
| Trường hợp | Người thực hiện | Điều kiện |
|---|---|---|
| Nhân viên tự trả | Agent | Bấm “Trả về Pool” trên từng khách |
| Tự động thu hồi | System | Khách trong giỏ > auto_return_days ngày mà không có hoạt động nào |
| Manager thu hồi | Manager | Thu hồi thủ công 1 hoặc nhiều khách |
Khi trả về Pool:
- Khách trở lại Pool gốc.
- Nhật ký ghi: “Trả về Pool bởi [Nguồn] lúc [Thời gian]”.
- Lịch sử xử lý trước đó của nhân viên cũ được giữ nguyên.
[F7] Báo cáo & Theo dõi (Manager)
Manager xem được:
- Số khách trong Pool / đã được nhận / đang trong giỏ của từng nhân viên.
- Số ngày trung bình khách nằm trong giỏ trước khi được xử lý.
- Tỷ lệ Action Gate hoàn thành theo nhân viên.
- Danh sách khách bị thu hồi tự động (overdue).
3. Danh sách nghiệp vụ
| # | Business Rule |
|---|---|
| BR-01 | Một khách hàng chỉ có thể thuộc 1 giỏ cá nhân tại một thời điểm. Khi đã được nhận, khách sẽ ẩn khỏi Pool cho đến khi được trả về. |
| BR-02 | Nhân viên không thể lấy thêm khi Giỏ đã đạt max_capacity, kể cả khi Action Gate đã thoả mãn. |
| BR-03 | Action Gate được đánh giá tại thời điểm bấm “Lấy vào Giỏ”. Trạng thái trong giỏ thay đổi sau đó không bị hồi tố. |
| BR-04 | Manager có thể override Action Gate cho nhân viên cụ thể (cấp quyền đặc biệt). |
| BR-05 | Khách bị thu hồi tự động sau auto_return_days ngày không có hoạt động – giá trị mặc định là 3 ngày, Manager có thể chỉnh. |
| BR-06 | Lịch sử xử lý của nhân viên cũ (ghi chú, cuộc gọi) không bị xoá khi khách được trả về Pool hoặc nhận bởi người khác. |
| BR-07 | pick_mode = auto: Hệ thống tự chọn ngẫu nhiên hoặc theo Lead Score (tuỳ cấu hình) thay vì để nhân viên chọn tay. |
| BR-08 | Một Pool có thể được gán cho nhiều nhóm / nhân viên. Các Pool khác nhau có thể chia sẻ cùng một tập khách (khách xuất hiện ở nhiều Pool cho đến khi được nhận). |
4. Giao diện
| Màn hình | Mô tả |
|---|---|
| Pool List (Manager) | Bảng tất cả Pool đang active: Tên, số khách, số NV gán, trạng thái |
| Pool Config (Manager) | Form tạo/sửa Pool: Bộ lọc, Giới hạn, Action Gate, Gán NV |
| Pool View (Agent) | Danh sách khách trong Pool, bộ lọc nhanh, nút “Lấy vào Giỏ” |
| My Basket (Agent) | Giỏ cá nhân: danh sách khách đang giữ, thanh tiến độ Action Gate |
| Dashboard (Manager) | Báo cáo: tình trạng Pool, hiệu suất từng NV, khách overdue |
🔗 Figma: [TODO – link thiết kế]
III. Yêu cầu phi chức năng
- Hiệu năng: API lấy khách (
POST /lead-pool/claim) phải trả về response < 2 giây dù Pool có đến 10.000 khách. - Concurrency: Hệ thống phải xử lý race condition khi 2 nhân viên cùng lấy cùng 1 khách một lúc – đảm bảo chỉ 1 người nhận được, người còn lại nhận thông báo “Khách vừa được người khác lấy”.
- Audit log: Mọi hành động trên khách trong Pool (lấy, trả, thu hồi, ghi cuộc gọi) đều phải được ghi log với timestamp và actor.
- Rate limit: Nhân viên không thể gửi quá 10 request “Lấy vào Giỏ” trong vòng 1 phút (chống spam/bot).
- Bảo mật: Nhân viên chỉ thấy được Pool mà họ được gán, không thấy giỏ của nhân viên khác (trừ Manager).
IV. Dependency (Liên quan & Phụ thuộc)
| Module / Feature | Quan hệ |
|---|---|
| Contact / Lead module | Pool đọc và lọc từ bảng Contact/Lead. Khi khách được nhận, trường assigned_to và in_pool được cập nhật. |
| Activity Log (Cuộc gọi, Ghi chú) | Action Gate đọc từ bảng Activity để kiểm tra điều kiện mở khoá. |
| Permission / Role | Phân quyền Manager vs Agent, cấu hình ai được tạo/sửa Pool. |
| Campaign module | Khách từ một chiến dịch có thể được đổ tự động vào một Pool cụ thể sau khi kết thúc. |
| Notification | Gửi thông báo cho NV khi: bị thu hồi khách, khách overdue, Action Gate vừa mở khoá. |
| Report / Analytics | Pool cần cung cấp data cho Dashboard báo cáo hiệu suất. |
V. API Contract (Dev viết)
API 1: Lấy khách vào Giỏ
- Method & Endpoint:
POST /api/v1/lead-pool/{pool_id}/claim - Request Body:
{ "contact_ids": ["c_001", "c_002", "c_003"] } - Response 200 (Thành công):
{ "claimed": ["c_001", "c_002", "c_003"], "failed": [], "basket_count": 11, "basket_capacity": 30 } - Response 200 (Một phần thất bại – race condition):
{ "claimed": ["c_001", "c_003"], "failed": [{"id": "c_002", "reason": "Đã được người khác lấy"}], "basket_count": 10, "basket_capacity": 30 } - Response 403 (Action Gate chưa thoả mãn):
{ "error": "ACTION_GATE_LOCKED", "message": "Bạn cần ghi nhận cuộc gọi cho 3 khách nữa trước khi lấy thêm.", "gate_progress": { "current": 2, "required": 5, "type": "log_call" } } - Response 403 (Giỏ đã đầy):
{ "error": "BASKET_FULL", "message": "Giỏ của bạn đã đạt giới hạn 30 khách." }
API 2: Trả khách về Pool
- Method & Endpoint:
POST /api/v1/lead-pool/{pool_id}/return - Request Body:
{ "contact_ids": ["c_001"] } - Response 200:
{ "returned": ["c_001"] }
API 3: Lấy trạng thái Giỏ cá nhân
- Method & Endpoint:
GET /api/v1/lead-pool/my-basket - Response 200:
{ "basket_count": 8, "basket_capacity": 30, "action_gate": { "type": "log_call", "current": 3, "required": 5, "is_locked": true }, "contacts": [ ... ] }
VI. Test case
| # | Loại | Mô tả | Kết quả mong đợi |
|---|---|---|---|
| TC-01 | Happy Path | NV lấy 5 khách, giỏ còn chỗ, Action Gate đã thoả mãn | 5 khách vào giỏ, khách biến mất khỏi Pool |
| TC-02 | Business Rule | NV lấy 5 khách nhưng giỏ chỉ còn 3 chỗ | Hệ thống báo lỗi “Giỏ không đủ chỗ”, chặn toàn bộ |
| TC-03 | Business Rule | NV lấy 5 khách, Action Gate log_call yêu cầu 5 cuộc gọi nhưng mới ghi 3 |
Nút “Lấy vào Giỏ” bị khoá, hiển thị “Cần ghi nhận 2 cuộc gọi nữa” |
| TC-04 | Concurrency | 2 NV cùng lấy cùng 1 khách trong cùng 1 giây | 1 NV lấy thành công, 1 NV nhận lỗi “Khách đã được người khác lấy” |
| TC-05 | Auto-return | Khách trong giỏ > 3 ngày không có hoạt động | Hệ thống tự trả khách về Pool, ghi log tự động |
| TC-06 | Edge Case | NV lấy khách cuối cùng còn trong Pool | Pool hiển thị 0 khách, NV khác thấy Pool trống |
| TC-07 | Permission | NV không được gán vào Pool cố truy cập Pool | Hệ thống trả 403, không hiển thị data |
| TC-08 | Manager Override | Manager tắt Action Gate cho 1 NV cụ thể | NV đó lấy được khách dù chưa thoả mãn Action Gate |