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ượngphả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_threshold do 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_toin_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