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 đáp ứng Action Gate (bộ lọc trạng thái) trước khi được lấy thêm.
Thiết kế tích hợp: Lead Pool không phải module riêng mà là một chế độ cấu hình bên trong Danh sách (List Object). Manager bật chế độ “Giỏ khách hàng” ngay tại màn hình cài đặt Danh sách.
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 (trong Danh sách)
User story: Là một Manager, tôi muốn bật chế độ "Giỏ khách hàng" cho một Danh sách có sẵn, cấu hình 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.
| Tên Use Case | [UC-01] Bật & Cấu hình Lead Pool trong Danh sách |
|---|---|
| Actor | Manager, Admin |
| Pre-condition | Đã đăng nhập, có quyền chỉnh sửa Danh sách |
| Main Flow | 1. Manager vào Danh sách → [Tên danh sách] → Cài đặt. 2. Bật toggle “Chế độ Giỏ khách hàng”. 3. Cấu hình Bộ lọc Pool – chỉ liên hệ thoả mãn bộ lọc mới xuất hiện trong Pool (xem bên dưới). 4. Cấu hình Giới hạn lấy (xem bên dưới). 5. Cấu hình Action Gate – bộ lọc điều kiện để mở khoá lấy thêm (xem bên dưới). 6. Gán Pool cho nhân viên / nhóm cụ thể (hoặc tất cả trong danh sách). 7. Bấm Lưu → Pool được kích hoạt. |
| Exception | - Thiếu bộ lọc Pool: Hệ thống báo “Vui lòng chọn ít nhất 1 điều kiện lọc Pool”. - Giới hạn lấy = 0: Không cho lưu. - Action Gate không có bộ lọc: Hệ thống báo “Vui lòng cấu hình bộ lọc Action Gate”. |
| Post-condition | Danh sách chuyển sang chế độ Pool; nhân viên được gán thấy tab “Giỏ khách hàng” trong Danh sách 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 (Bộ lọc mở khoá lấy thêm):
Action Gate sử dụng bộ lọc liên hệ thay vì loại hành động cứng. Hệ thống kiểm tra: tất cả liên hệ đang trong giỏ cá nhân của nhân viên có thoả mãn bộ lọc Action Gate không? Nếu có → mở khoá lấy thêm.
| Trường bộ lọc | Loại điều kiện | Ví dụ dùng làm Action Gate |
|---|---|---|
| Trạng thái liên hệ | Là / không là | Trạng thái “Đã gọi”, “Đã chốt”, “Không tiềm năng” |
| Có cuộc gọi được ghi nhận | Có / không có | Mỗi liên hệ phải có ≥ 1 cuộc gọi |
| Có ghi chú | Có / không có | Mỗi liên hệ phải có ghi chú |
| Có task | Có / không có | Mỗi liên hệ phải có task chăm sóc |
| Ngày cập nhật cuối | Trong X ngày gần nhất | Cập nhật trong 2 ngày qua |
| Kết hợp nhiều điều kiện | AND / OR | Trạng thái “Đã gọi” AND có ghi chú |
Nguyên tắc: Điều kiện được đánh giá trên toàn bộ giỏ của nhân viên. Chỉ khi mọi liên hệ trong giỏ thoả mãn bộ lọc, nút “Lấy vào Giỏ” mới được mở khoá.
[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 (bộ lọc):
Lấy tất cả liên hệ đang trong giỏ cá nhân của nhân viên.
Áp dụng bộ lọc Action Gate đã cấu hình lên từng liên hệ.
IF có BẤT KỲ liên hệ nào KHÔNG thoả mãn bộ lọc → BLOCK
Thông báo: "Bạn còn [N] liên hệ chưa đạt điều kiện [mô tả bộ lọc]."
IF TẤT CẢ liên hệ thoả mãn bộ lọc → PASS
Trường hợp đặc biệt:
- Giỏ đang trống (0 liên hệ): Mặc định PASS (không có gì để kiểm tra).
- Manager override: Bỏ qua kiểm tra Action Gate cho nhân viên được cấp quyền.
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 | Vị trí | Mô tả |
|---|---|---|
| Danh sách → Cài đặt (Manager) | Trong Danh sách | Toggle bật chế độ Pool; form cấu hình Bộ lọc Pool, Giới hạn lấy, Action Gate, Gán NV |
| Danh sách → Pool (Agent) | Tab trong Danh sách | Danh sách liên hệ đang trong Pool, bộ lọc nhanh, nút “Lấy vào Giỏ” |
| Danh sách → Giỏ của tôi (Agent) | Tab trong Danh sách | Danh sách liên hệ đang trong giỏ cá nhân; banner trạng thái Action Gate |
| Danh sách → Báo cáo (Manager) | Tab trong Danh sách | Tình trạng Pool, số liên hệ theo NV, khách overdue, tỷ lệ hoàn thành |
🔗 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 3 liên hệ chưa đạt điều kiện: Trạng thái là 'Đã gọi'.", "gate_detail": { "filter_description": "Trạng thái là 'Đã gọi'", "total_in_basket": 8, "passed": 5, "failed": 3 } } - 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 liên hệ, giỏ còn chỗ, tất cả liên hệ trong giỏ đã thoả mãn bộ lọc Action Gate | 5 liên hệ vào giỏ, biến mất khỏi Pool |
| TC-02 | Business Rule | NV lấy 5 liên hệ nhưng giỏ chỉ còn 3 chỗ | Hệ thống báo “Giỏ không đủ chỗ”, chặn toàn bộ |
| TC-03 | Action Gate – bộ lọc | Giỏ có 8 liên hệ, bộ lọc Action Gate là “Trạng thái = Đã gọi”, còn 3 liên hệ trạng thái “Mới” | Nút “Lấy vào Giỏ” bị khoá; hiển thị “Còn 3 liên hệ chưa đạt điều kiện: Trạng thái là Đã gọi” |
| TC-04 | Action Gate – giỏ trống | NV chưa có liên hệ nào trong giỏ, bấm “Lấy vào Giỏ” | Action Gate tự động PASS (giỏ trống = không có liên hệ nào vi phạm) |
| TC-05 | Action Gate – tất cả pass | Tất cả liên hệ trong giỏ đã đổi trạng thái “Đã gọi” | Nút “Lấy vào Giỏ” mở khoá ngay lập tức |
| TC-06 | Concurrency | 2 NV cùng lấy cùng 1 liên hệ trong cùng 1 giây | 1 NV lấy thành công, 1 NV nhận lỗi “Liên hệ đã được người khác lấy” |
| TC-07 | Auto-return | Liên hệ trong giỏ > 3 ngày không có hoạt động | Hệ thống tự trả liên hệ về Pool, ghi log tự động |
| TC-08 | Permission | NV không được gán vào Danh sách Pool cố truy cập | Hệ thống trả 403, không hiển thị data |
| TC-09 | Manager Override | Manager bật override Action Gate cho 1 NV cụ thể | NV đó lấy được liên hệ dù giỏ chưa đủ điều kiện bộ lọc |