Lịch gọi khách hàng & Cơ chế gọi lại trong Chiến dịch Callbot
I. Tổng quan
Tài liệu đặc tả nghiệp vụ và kỹ thuật tính năng đặt lịch gọi riêng cho từng khách hàng, cấu hình chiến lược gọi lại (retry) khi gọi thất bại, và xử lý dời lịch (carry-over) khi không thực hiện kịp trong khung thời gian.
1. Vấn đề
- Trước đây, chiến dịch Callbot chỉ hỗ trợ cấu hình khung giờ và ngày chạy ở cấp độ toàn chiến dịch. Mọi khách hàng nhập vào đều được xử lý tuần tự ngay khi chiến dịch kích hoạt trong khung giờ hoạt động chung.
- Thực tế vận hành phát sinh nhu cầu tiếp cận khách hàng theo từng lịch hẹn cụ thể (ví dụ: tư vấn tài chính theo khung giờ khách rảnh, nhắc lịch tái khám đúng ngày hẹn, nhắc thanh toán vào một khung giờ xác định). Việc không có lịch riêng khiến tỷ lệ bận máy, không bắt máy tăng cao và gây phiền hà cho khách hàng.
- Khi cuộc gọi không thành công hoặc chưa kịp thực hiện do tắc nghẽn hạ tầng (hết kênh SIP, chạm hạn mức ngày, hết ngân sách), hệ thống thiếu cơ chế phân định rành mạch giữa Gọi thất bại (cần retry theo khoảng cách) và Không thực hiện kịp lịch (cần dời lịch mà không làm mất lượt gọi).
2. Giải pháp
- Cho phép thiết lập Lịch gọi riêng cho khách hàng (
Ngày gọi,Giờ bắt đầu) từ Bước 2 (Nhập khách hàng) qua 3 nguồn: File Excel/CSV, Chiến dịch khác, và CRM Contact. - Xây dựng Bộ phân phối cuộc gọi (Dispatch Engine) thông minh tại Bước 3:
- Tự động so khớp thời gian giữa Lịch khách hàng và Lịch chiến dịch: Cuộc gọi chỉ kích hoạt khi thời điểm hiện tại $T_{call}$ nằm trong khung giờ hoạt động của chiến dịch ($T_{call} \in [Start_{camp}, End_{camp}]$) và đã đến hoặc qua giờ hẹn của khách hàng ($T_{call} \ge Start_{cust}$).
- Phân định rõ ràng 2 cơ chế:
- Gọi lại khi thất bại (Retry on failure): Máy bận, Không nghe máy, Hộp thư thoại. Số lần gọi lại tính trên toàn chiến dịch (không reset mỗi ngày).
- Phạm vi tiếp tục khi không hoàn tất (Continuation Scope): Chỉ trong ngày hẹn / Cho dời sang ngày tiếp theo (có giới hạn).
- Phân loại trạng thái tinh gọn (Đang chờ, Chờ gọi lại, Đang gọi, Thành công, Thất bại, Hết hạn, Xung đột lịch) phục vụ lọc dữ liệu và giám sát chiến dịch real-time.
3. Đối tượng
- Campaign Manager / Sales Ops / BA: Thiết lập chiến dịch, cấu hình kịch bản, import danh sách khách hàng kèm lịch hẹn, thiết lập điều kiện gọi lại.
- Hệ thống điều phối Outbound (Dispatch Engine): Tự động quét hàng đợi, kiểm tra điều kiện tài nguyên, lịch hẹn và kích hoạt cuộc gọi qua tổng đài PBX.
- Khách hàng cuối: Được nhận cuộc gọi đúng vào ngày và khung giờ thuận tiện nhất.
4. Tầm nhìn / Insight (Mở rộng trong tương lai)
- Hỗ trợ lịch gọi lặp định kỳ (Recurring schedule) và số lần gọi lại riêng biệt theo từng ngày.
- Khung giờ linh hoạt theo múi giờ quốc tế khi mở rộng sang các thị trường nước ngoài.
- Tích hợp Trigger Adaflow khi khách hàng chuyển sang trạng thái “Hết hạn chưa gọi” để tự động kích hoạt kênh thay thế (ZNS/SMS/Email).
II. Yêu cầu chức năng
1. Danh sách tính năng
- Lựa chọn & Nhập lịch khách hàng tại Bước 2: Hỗ trợ 3 nguồn nhập (File, Chiến dịch khác, CRM Contact), xác thực tính hợp lệ của ngày và giờ bắt đầu, xử lý dữ liệu trùng lặp có kiểm soát.
- Cấu hình Chiến lược & Lịch chiến dịch tại Bước 3: Phối hợp giữa “Gọi ngay” / “Đặt lịch gọi” với lịch riêng của từng khách hàng.
- Cấu hình Gọi lại khi thất bại (Retry Strategy): Điều kiện kích hoạt retry (Máy bận, Không nghe máy, Hộp thư thoại), số lần retry toàn trình, khoảng cách giãn cách tối thiểu từ khi cuộc gọi trước kết thúc.
- Cấu hình Xử lý khi không hoàn tất lịch đã đặt (Continuation Scopes): 2 phạm vi (Chỉ trong ngày hẹn, Cho dời sang ngày sau có giới hạn).
- Bộ điều phối cuộc gọi & Xử lý xung đột (Dispatch Engine): 5 điều kiện tiên quyết kích hoạt cuộc gọi, giải quyết xung đột lịch, quản lý mốc thời gian hoạt động của chiến dịch $[Start_{camp}, End_{camp})$.
- Quản lý & Theo dõi trạng thái khách hàng: Hệ thống trạng thái chuẩn hóa, hiển thị lý do chờ/dừng, thống kê báo cáo chi tiết.
2. Đặc tả chi tiết
2.1. Bước 2 — Nhập khách hàng & Thiết lập lịch gọi
Tại màn hình Bước 2 (Nhập khách hàng), bổ sung khu vực cấu hình Lịch gọi khách hàng tương ứng theo từng nguồn nạp:
| Nguồn nhập | Tùy chọn đặt lịch | Quy tắc xử lý nghiệp vụ |
|---|---|---|
| File (Excel / CSV) | 1. Theo lịch chiến dịch 2. Đặt lịch chung cho đợt nhập 3. Lấy ngày và giờ bắt đầu từ các cột trong file |
• Chọn nguồn 3: Cho phép map cột Ngày gọi, Giờ bắt đầu.• Dòng thiếu ngày hoặc sai định dạng: Báo lỗi dòng, không import. • File mới hoặc file đã lưu trong thư viện đều áp dụng cơ chế này. |
| Chiến dịch khác (Import / Clone) | 1. Theo lịch chiến dịch đích 2. Đặt lịch chung mới 3. Giữ lịch riêng từ chiến dịch nguồn |
• Chọn nguồn 3: Nếu ngày/giờ bắt đầu từ chiến dịch nguồn đã qua ở thời điểm hiện tại, dòng đó bị báo lỗi hết hạn; không tự đổi sang ngày mới. • Số lần đã gọi từ chiến dịch nguồn không cộng dồn vào chiến dịch đích. |
| Danh sách liên hệ (CRM Contacts) | 1. Theo lịch chiến dịch 2. Đặt lịch chung cho đợt nhập 3. Lấy lịch từ các trường trên Contact |
• Chọn nguồn 3: Map trường ngày và giờ bắt đầu trên đối tượng Contact. • Dữ liệu lịch được chốt snapshot tại thời điểm nạp. Thay đổi trên Contact sau đó không làm đổi lịch trong chiến dịch. |
Quy tắc xác thực trường dữ liệu lịch:
Ngày gọi(call_date): Định dạngYYYY-MM-DD. Bắt buộc nếu có thiết lập lịch riêng. Phải là ngày hiện tại hoặc tương lai (không cho phép ngày quá khứ).Giờ bắt đầu(start_time): Định dạngHH:mm.- Thể hiện thời điểm sớm nhất bắt đầu được phép gọi cho khách hàng trong ngày hẹn.
- Hệ thống không cam kết gọi chính xác từng giây/phút tại mốc
start_timekhi có nhiều khách hàng cùng đến giờ hẹn hoặc tài nguyên bận (nghẽn kênh SIP, chạm quota). Khách hàng sẽ được xếp vào hàng đợi ưu tiên và kích hoạt cuộc gọi ngay khi có kênh khả dụng từ thời điểmstart_timetrở đi.
- Tình huống dữ liệu:
- Chỉ có ngày (để trống giờ bắt đầu): Được chấp nhận. Hệ thống sẽ bắt đầu gọi ngay từ đầu khung giờ hoạt động hợp lệ đầu tiên của chiến dịch trong ngày đó.
- Có giờ bắt đầu nhưng thiếu ngày: Báo lỗi validate, yêu cầu bổ sung ngày gọi.
- Có cả ngày và giờ bắt đầu: Khách hàng chỉ được phép gọi từ thời điểm
start_timetrở đi trong các khung giờ hoạt động của chiến dịch vào ngày đó. - Không có ngày và giờ: Xử lý hoàn toàn theo lịch hoạt động chung của chiến dịch.
- Xử lý khi khách hàng/SĐT đã tồn tại trong chiến dịch:
- Báo trùng số điện thoại.
- Cho phép người dùng lựa chọn cập nhật lịch có kiểm soát cho khách hàng còn đang trong diện xử lý.
- Tuyệt đối không tự tạo thêm lượt gọi, không xóa lịch sử gọi cũ, và không reset số lần đã gọi.
- Khách hàng đã ở trạng thái kết thúc (
COMPLETED,FAILED,EXPIRED) không tự động được gọi lại khi re-import (muốn gọi tiếp phải có thao tác Đặt lại lịch/Kích hoạt lại rõ ràng). - Một khách hàng có nhiều SĐT sẽ áp dụng chung một lịch hẹn trong phiên bản này.
2.2. Bước 3 — Cấu hình chiến lược & Lên lịch gọi
A. Phối hợp Lịch chiến dịch và Lịch riêng khách hàng
| Cấu hình | Ý nghĩa vận hành |
|---|---|
| Gọi ngay (Immediate) | Chiến dịch bắt đầu xử lý ngay lập tức khi kích hoạt. Khách hàng có lịch riêng vẫn phải chờ đến đúng lịch hẹn mới được gọi. |
| Đặt lịch gọi (Scheduled) | Toàn bộ chiến dịch chỉ bắt đầu từ ngày cấu hình và chỉ chạy trong các khung giờ cho phép của chiến dịch. |
| Quy tắc phối hợp thời gian | Để một cuộc gọi được kích hoạt tại thời điểm hiện tại $T$ trong ngày hẹn ($CallDate_{cust}$), bắt buộc phải thỏa mãn đồng thời: 1. $T$ nằm trong khung giờ hoạt động của chiến dịch: $T \in [Start_{camp}, End_{camp}]$ 2. $T$ đã đến hoặc qua giờ bắt đầu hẹn của khách: $T \ge Start_{cust}$ Nếu giờ bắt đầu của khách muộn hơn hoặc bằng toàn bộ các khung giờ hoạt động của chiến dịch trong ngày hẹn ($Start_{cust} \ge \max(End_{camp})$) $\rightarrow$ Báo Xung đột lịch ( SCHEDULE_CONFLICT). Hệ thống không tự ý bỏ qua lịch khách hoặc lịch chiến dịch. |
B. Cấu hình Gọi lại khi thất bại (Retry Strategy)
| Thuộc tính | Kiểu / Lựa chọn | Mặc định | Ràng buộc nghiệp vụ |
|---|---|---|---|
| Bật/Tắt gọi lại | Toggle (On/Off) | On | Khi Tắt: Khách hàng có kết quả thất bại sẽ dừng ngay, không gọi thêm. |
| Điều kiện gọi lại | Multi-select: Máy bận, Không nghe máy, Hộp thư thoại |
Chọn cả 3 | Chỉ kích hoạt retry khi kết quả cuộc gọi thuộc các điều kiện được chọn. Lỗi hệ thống/hạ tầng được xếp vào luồng xử lý riêng. |
| Số lần gọi lại | Number (1 – 5 lần) | 2 lần | Tối đa thực hiện $N+1$ cuộc (1 cuộc đầu + $N$ cuộc gọi lại). Giới hạn tối đa 5 lần. |
| Khoảng cách gọi lại | Number (phút) | 15 phút | Khoảng cách tối thiểu giữa 2 lần gọi liên tiếp. Tối thiểu 5 phút, Tối đa 1440 phút (24h). Không cho phép nhập 0 phút. |
Nguyên tắc cốt lõi về Retry:
- Số lần gọi lại được tính lũy kế trên toàn quá trình của khách hàng trong chiến dịch, tuyệt đối không reset mỗi ngày.
- Khi đã hết số lần gọi lại cấu hình, khách hàng chuyển sang trạng thái Thất bại (
FAILED), lý doHết số lần gọi lại(MAX_RETRIES_EXCEEDED), kể cả khi vẫn còn hạn dời ngày.- Khoảng cách gọi lại ($M$ phút) được tính từ thời điểm cuộc gọi trước kết thúc (
last_call_ended_at), không tính từ thời điểm bắt đầu gọi.- Đầu số gọi ra (Caller ID xoay vòng theo lượt) tiếp tục tăng theo thứ tự lượt thực hiện thực tế, không bị quay lại lượt đầu khi chuyển ngày.
C. Xử lý khi không hoàn tất trong lịch đã đặt (Continuation Scopes)
Người dùng bắt buộc chọn một trong hai phạm vi tiếp tục gọi sau đây:
graph TD
A[Cuộc gọi chưa hoàn tất hoặc thất bại cần retry] --> B{Phạm vi tiếp tục được cấu hình}
B -->|1. Chỉ trong ngày hẹn| C{Còn trong khung giờ hoạt động hôm nay?}
C -->|Còn| C1[Tiến hành gọi/retry từ start_time]
C -->|Hết ngày/Hết ca| C2[Dừng: Chuyển trạng thái EXPIRED hoặc FAILED]
B -->|2. Cho tiếp tục sang ngày sau| D{Số ngày dời <= Giới hạn cho phép?}
D -->|Còn hạn| E[Dời sang ngày tiếp theo, gọi từ start_time]
D -->|Hết hạn| F[Dừng: Chuyển trạng thái EXPIRED hoặc FAILED]
- Chỉ trong ngày hẹn (
SAME_DAY):- Cuộc gọi (và các lượt gọi lại nếu có) chỉ được thực hiện từ mốc
start_timecho đến khi kết thúc khung giờ hoạt động cuối cùng của chiến dịch trong ngày hẹn (call_date). - Hết ngày hẹn mà chưa kịp gọi (nghẽn kênh, hết quota) $\rightarrow$ Dừng xử lý và chuyển sang trạng thái Hết hạn (
EXPIRED). - Hết ngày hẹn mà cuộc gọi trước thất bại nhưng không kịp khoảng cách retry trước khi hết giờ chiến dịch $\rightarrow$ Dừng xử lý và chuyển sang trạng thái Thất bại (
FAILED), lý doEXPIRED_AFTER_FAILED.
- Cuộc gọi (và các lượt gọi lại nếu có) chỉ được thực hiện từ mốc
- Cho tiếp tục sang ngày sau (
NEXT_DAYS):- Cho phép dời sang ngày hoạt động tiếp theo của chiến dịch khi hết ngày hẹn mà chưa gọi được hoặc cuộc gọi thất bại vẫn còn lượt retry.
- Bắt buộc nhập Số ngày chuyển thêm ($K$ ngày, từ 1 đến 7 ngày, mặc định 1 ngày). Hạn chót =
Ngày gốc + K ngày. - Bất biến bảo toàn giờ bắt đầu: Khi dời sang ngày tiếp theo, cuộc gọi vẫn chỉ được kích hoạt từ mốc
start_timetrở đi trong các khung giờ hoạt động của chiến dịch ở ngày đó, tuyệt đối không tự ý gọi sớm hơn trước giờ hẹn.
Phân định quan trọng giữa Chưa gọi và Gọi thất bại:
- Chưa từng được gọi (do thiếu kênh, chạm hạn mức, pause): Nếu cấu hình cho dời sang ngày sau, khách hàng vẫn được chuyển lịch sang ngày sau bình thường, ngay cả khi tính năng Gọi lại (Retry) bị TẮT.
- Đã gọi thất bại: Chỉ được chuyển lịch sang ngày sau nếu tính năng Gọi lại đang BẬT và khách hàng VẪN CÒN LƯỢT GỌI LẠI.
2.3. Bộ điều phối cuộc gọi (Dispatch Engine) & Điều kiện kích hoạt
Để cuộc gọi được khởi chạy tới PBX, khách hàng phải thỏa mãn đồng thời 5 điều kiện tiên quyết:
- Chiến dịch hợp lệ: Chiến dịch đang ở trạng thái
Active, nằm trong khoảng ngày hiệu lực và trong khung giờ hoạt động của chiến dịch. - Giờ hẹn khách hàng hợp lệ: Thời điểm hiện tại $T$ đã đến hoặc qua giờ bắt đầu hẹn của khách: $T \ge Start_{cust}$ (với ngày = $CallDate_{cust}$).
- Điều kiện Retry hợp lệ:
- Nếu là cuộc gọi đầu: Chưa từng gọi.
- Nếu là cuộc gọi lại: Kết quả cuộc trước thuộc điều kiện retry, thời điểm hiện tại $T \ge LastEndedAt + Interval$, và $CallCount < MaxAttempts$.
- Tài nguyên khả dụng: Còn kênh đồng thời (Concurrent channels > 0), chưa chạm hạn mức gọi trong ngày (Daily quota), và số dư tài khoản/ví đủ đáp ứng.
- Điều kiện đối tượng: Khách hàng chưa ở trạng thái kết thúc (
COMPLETED,FAILED,EXPIRED), chưa bị dừng xử lý, và SĐT không thuộc danh sách Chặn cuộc gọi (DNC - Do Not Call).
Quy tắc mốc thời gian hoạt động của chiến dịch $[Start_{camp}, End_{camp})$:
- Khách hàng có
start_timeđược phép bắt đầu gọi từ chính xác thời điểmstart_time(nếu chiến dịch đang trong khung giờ hoạt động) hoặc từ mốcStart_{camp}mở đầu khung hoạt động kế tiếp (nếustart_timerơi vào khoảng nghỉ giữa các ca). - Tuyệt đối không được bắt đầu cuộc gọi mới tại hoặc sau thời điểm kết thúc khung giờ hoạt động của chiến dịch (
End_{camp}). - Cuộc gọi đã được kết nối trước thời điểm
End_{camp}(ví dụ bắt đầu lúc 11:29 với ca sáng kết thúc lúc 11:30) được phép tiếp tục đàm thoại bình thường cho đến khi kết thúc, không bị ngắt máy đột ngột.
2.4. Danh mục Trạng thái & Báo cáo Real-time
Hệ thống quản lý trạng thái điều phối khách hàng (dispatch_status) theo bộ trạng thái tinh gọn, phân định rõ ràng giữa các giai đoạn xử lý:
| Mã trạng thái | Tên hiển thị | Nhóm | Ý nghĩa & Hành vi |
|---|---|---|---|
WAITING |
Đang chờ | Chờ xử lý | Đã nạp vào chiến dịch, đang chờ đến giờ hẹn (start_time) hoặc chờ tài nguyên (kênh SIP, hạn mức ngày, ngân sách) để kích hoạt cuộc gọi đầu tiên. |
WAITING_RETRY |
Chờ gọi lại | Chờ xử lý | Cuộc gọi trước thất bại, đang đếm ngược thời gian giãn cách (interval_minutes) trước khi thử lại lượt tiếp theo. |
CALLING |
Đang gọi | Đang xử lý | Cuộc gọi đang được tổng đài xử lý, đổ chuông hoặc đang trong quá trình đàm thoại với khách hàng. |
COMPLETED |
Thành công | Kết thúc | Cuộc gọi đã kết nối thành công, bot hoàn thành kịch bản hội thoại. Kết thúc chu trình xử lý cho khách hàng này. |
FAILED |
Thất bại | Kết thúc | Đã thực hiện cuộc gọi nhưng thất bại và kết thúc chu trình xử lý (hết số lần retry, tắt tính năng gọi lại, hoặc hết hạn dời lịch sau cuộc gọi thất bại). Lý do cụ thể ghi nhận tại stop_reason. |
EXPIRED |
Hết hạn | Kết thúc | Hết ngày hẹn hoặc hết số ngày cho phép dời lịch mà chưa từng được thực hiện cuộc gọi nào (do nghẽn kênh SIP, chạm hạn mức ngày, hết ngân sách hoặc tạm dừng chiến dịch). |
SCHEDULE_CONFLICT |
Xung đột lịch | Ngoại lệ | Giờ bắt đầu hẹn của khách hàng (start_time) muộn hơn hoặc bằng toàn bộ giờ kết thúc hoạt động của chiến dịch trong ngày. Chờ người dùng điều chỉnh. |
Bảng phân loại chi tiết nguyên nhân kết thúc (stop_reason):
| Trạng thái chính | Mã lý do (stop_reason) |
Tên hiển thị lý do | Diễn giải chi tiết |
|---|---|---|---|
FAILED |
MAX_RETRIES_EXCEEDED |
Hết số lần gọi lại | Đã gọi đủ $N+1$ cuộc nhưng kết quả vẫn là Máy bận / Không nghe máy / Hộp thư thoại. |
FAILED |
RETRY_DISABLED_FAILED |
Thất bại (Không gọi lại) | Cuộc gọi đầu thất bại khi tính năng Gọi lại (Retry) bị Tắt. |
FAILED |
EXPIRED_AFTER_FAILED |
Hết hạn dời lịch sau thất bại | Đã gọi thất bại, vẫn còn lượt retry nhưng đã hết thời hạn dời lịch cho phép. |
FAILED |
CALL_REJECTED_DNC |
Bị chặn (Danh sách DNC) | Số điện thoại nằm trong danh sách không làm phiền hoặc bị nhà mạng chặn. |
EXPIRED |
OUT_OF_RESOURCE |
Hết hạn — nghẽn tài nguyên | Hết thời hạn dời lịch mà chưa từng được gọi do thiếu kênh SIP hoặc chạm quota ngày. |
EXPIRED |
CAMPAIGN_ENDED_UNREACHED |
Hết hạn — chiến dịch kết thúc | Chiến dịch đã kết thúc trước khi khách hàng đến lượt thực hiện cuộc gọi đầu tiên. |
3. Danh sách nghiệp vụ (Business Rules)
- Phân biệt rành mạch giữa Gọi thất bại và Không kịp lịch:
- “Gọi lại” (Retry) áp dụng cho cuộc gọi đã thực hiện nhưng không thành công (Máy bận, Không nghe máy, Voicemail).
- “Chuyển lịch / Dời lịch” (Carry-over) áp dụng cho việc bảo lưu lượt gọi hợp lệ sang thời điểm tiếp theo. Dời lịch tuyệt đối không cấp thêm lượt gọi.
- Bảo toàn giờ bắt đầu hẹn:
- Khi dời lịch sang ngày tiếp theo, hệ thống bắt buộc giữ nguyên mốc
start_timecủa khách hàng (chỉ kích hoạt cuộc gọi từstart_timetrở đi trong các khung giờ hoạt động của chiến dịch ngày tiếp theo), không được tự ý gọi sớm hơn trước giờ hẹn.
- Khi dời lịch sang ngày tiếp theo, hệ thống bắt buộc giữ nguyên mốc
- Quy tắc tính lượt và giãn cách:
- $N$ lần gọi lại tương ứng tối đa $N+1$ cuộc gọi. Lượt gọi được tính lũy kế toàn chiến dịch.
- Khoảng cách retry được tính từ thời điểm ngắt máy cuộc gọi trước (
last_call_ended_at).
- Phân loại lỗi hệ thống (System Error):
- Lỗi kỹ thuật tổng đài (SIP 503, SIP 500, lỗi đường truyền, hết kênh hệ thống): Không tính là 1 lượt gọi của khách hàng. Hệ thống tự động đẩy vào hàng đợi thử lại sau 1–3 phút theo cơ chế Backoff.
- Lỗi từ phía người nghe (486 Busy, 480/408 No Answer, Voicemail): Tính là 1 lượt gọi thất bại.
- Xử lý xung đột lịch trước khi chạy chiến dịch:
- Khi kích hoạt chiến dịch, nếu phát hiện khách hàng bị xung đột lịch, hệ thống hiển thị cảnh báo cho phép người dùng chọn:
- (A) Vẫn kích hoạt chiến dịch cho các khách hàng hợp lệ; đưa các khách xung đột vào trạng thái
SCHEDULE_CONFLICTđể cập nhật sau. - (B) Dừng lại để cấu hình lại lịch chiến dịch hoặc cập nhật danh sách khách hàng.
- (A) Vẫn kích hoạt chiến dịch cho các khách hàng hợp lệ; đưa các khách xung đột vào trạng thái
- Khi kích hoạt chiến dịch, nếu phát hiện khách hàng bị xung đột lịch, hệ thống hiển thị cảnh báo cho phép người dùng chọn:
- Thay đổi cấu hình khi chiến dịch đang chạy:
- Cấu hình mới áp dụng cho các lượt gọi chưa thực hiện.
- Cuộc gọi đang diễn ra được tiếp tục cho đến khi kết thúc.
- Thay đổi cấu hình không tự ý mở lại hoặc gọi thêm cho các khách hàng đã ở trạng thái kết thúc (
COMPLETED,FAILED, hoặcEXPIRED).
4. Giao diện người dùng (UI/UX)
- Bước 2 (Nhập khách hàng):
- Thêm Radio Group: “Lịch gọi khách hàng”:
- ⚪ Theo lịch chiến dịch
- ⚪ Đặt lịch chung cho đợt nhập (Hiển thị Date Picker + Time Picker chọn Giờ bắt đầu)
- ⚪ Lấy từ cột trong file / Trường Contact (Hiển thị Dropdown mapping cột:
Ngày gọi,Giờ bắt đầu).
- Bảng xem trước dữ liệu: Đánh dấu đỏ các dòng có lịch sai/lỗi (ngày quá khứ, sai định dạng ngày/giờ), hiển thị lý do lỗi chi tiết.
- Thêm Radio Group: “Lịch gọi khách hàng”:
- Bước 3 (Cấu hình gọi):
- Section “Chiến lược & Lên lịch”: Lựa chọn “Gọi ngay” hoặc “Đặt lịch gọi theo khung giờ”.
- Section “Cấu hình gọi lại”:
- Toggle Bật/Tắt gọi lại.
- Checkbox nhóm điều kiện:
Máy bận,Không nghe máy,Hộp thư thoại. - Input Number “Số lần gọi lại” (1 – 5 lần).
- Input Number “Khoảng cách giữa các lần” (5 – 1440 phút).
- Section “Xử lý khi không hoàn tất lịch”:
- Tooltip tiêu đề section (ℹ️): “Quy định cách hệ thống xử lý khi khách hàng chưa kịp được gọi (do nghẽn kênh, hết quota) hoặc cuộc gọi thất bại nhưng chưa hoàn tất đủ số lần gọi lại trước khi chiến dịch kết thúc giờ hoạt động trong ngày.”
- Radio 1: Chỉ trong ngày hẹn (
SAME_DAY)- Tooltip / Helper text: “Chỉ thực hiện cuộc gọi và các lượt gọi lại trong ngày hẹn đã đặt (bắt đầu từ Giờ bắt đầu đến khi hết khung giờ hoạt động của chiến dịch trong ngày). Hết ngày hẹn mà chưa hoàn tất, hệ thống sẽ dừng xử lý và chuyển trạng thái Hết hạn.”
- Radio 2: Cho tiếp tục sang ngày sau (
NEXT_DAYS)- Tooltip / Helper text: “Tự động dời lịch sang ngày hoạt động tiếp theo của chiến dịch nếu hết ngày mà chưa kịp gọi hoặc vẫn còn lượt gọi lại. Sang ngày mới, cuộc gọi vẫn chỉ kích hoạt từ Giờ bắt đầu đã đặt và bảo toàn số lần đã gọi.”
- $\rightarrow$ Hiển thị ô nhập “Số ngày chuyển thêm tối đa” (1 – 7 ngày, mặc định: 1 ngày):
- Tooltip ô nhập: “Số ngày hoạt động tối đa được phép dời lịch tính từ ngày hẹn gốc (Hạn chót = Ngày gốc + K ngày). Quá thời hạn này mà vẫn chưa hoàn tất, khách hàng sẽ dừng xử lý và chuyển trạng thái Hết hạn.”
- Hộp tóm tắt cấu hình (Rule Summary Box): Tự động hiển thị câu diễn giải bằng ngôn ngữ tự nhiên:
“Mỗi khách được gọi tối đa 3 cuộc, cách nhau ít nhất 15 phút. Khách có lịch riêng chỉ được gọi từ giờ đã đặt; có thể chuyển thêm tối đa 2 ngày. Số lần gọi không reset khi chuyển ngày.”
- Trang Chi tiết & Báo cáo chiến dịch:
- Tab “Danh sách khách hàng”: Bổ sung các cột:
Lịch hẹn yêu cầu,Lịch dự kiến tiếp theo,Số lần đã gọi,Lý do chờ / dừng. - Bộ lọc nâng cao: Bổ sung bộ lọc theo
Trạng thái điều phối(dispatch_status) vàNgày hẹn.
- Tab “Danh sách khách hàng”: Bổ sung các cột:
III. Yêu cầu phi chức năng
- Hiệu năng Dispatcher Worker: Bộ quét hàng đợi (Queue Poller) phải quét và quyết định điều phối cuộc gọi với độ trễ $\le 30$ giây đối với tệp dữ liệu lên đến $100,000$ khách hàng.
- Đồng thời & Khóa phân tán (Distributed Lock): Sử dụng Redis Lock hoặc Database Row-level Lock (
SELECT FOR UPDATE SKIP LOCKED) khi worker lấy khách hàng ra xử lý, đảm bảo tuyệt đối không xảy ra race condition gọi trùng 1 khách hàng 2 lần cùng lúc. - Audit Logging & Truy vết: Ghi log chi tiết mỗi sự kiện: thời điểm chuyển lịch, số ngày đã dời, lý do kết thúc xử lý, mã SIP phản hồi của từng cuộc gọi.
- Tính chính xác về thời gian: Toàn bộ logic so sánh thời gian sử dụng múi giờ chuẩn Việt Nam (
Asia/Ho_Chi_Minh/ GMT+7).
IV. Dependency (Liên quan & Phụ thuộc)
- PBX & SIP Trunk Service: Tiếp nhận lệnh quay số, trả về mã trạng thái cuộc gọi (Connected, Busy 486, No Answer 480, Unallocated 404, Service Unavailable 503).
- Module Quản lý Danh bạ (CRM Contact): Nguồn trích xuất thông tin khách hàng và trường lịch hẹn; tiếp nhận ghi log kết quả cuộc gọi.
- Campaign Real-time Report Service: Lắng nghe webhook kết thúc cuộc gọi để cập nhật số liệu thống kê và trạng thái khách hàng theo thời gian thực.
- Adaflow Trigger Service: Phát sinh sự kiện khi trạng thái khách hàng chuyển sang
EXPIRED_UNREACHEDhoặcCOMPLETED.
V. API Contract (Đặc tả kỹ thuật)
1. Mô hình dữ liệu mở rộng
Bảng call_campaign_customer:
ALTER TABLE call_campaign_customer ADD COLUMN (
schedule_type VARCHAR(20) DEFAULT 'CAMPAIGN_SCHEDULE', -- CAMPAIGN_SCHEDULE | BATCH_COMMON | CUSTOM_ROW
call_date DATE NULL, -- Ngày hẹn gọi (YYYY-MM-DD)
start_time TIME NULL, -- Giờ bắt đầu hẹn gọi (HH:mm:ss)
original_call_date DATE NULL, -- Ngày hẹn gốc trước khi dời lịch
carried_over_days INT DEFAULT 0, -- Số ngày đã dời thực tế
call_count INT DEFAULT 0, -- Tổng số cuộc đã gọi (bao gồm retry)
last_call_ended_at TIMESTAMP NULL, -- Thời điểm kết thúc cuộc gọi gần nhất
next_attempt_at TIMESTAMP NULL, -- Thời điểm sớm nhất được phép gọi tiếp theo
dispatch_status VARCHAR(20) DEFAULT 'WAITING', -- WAITING | WAITING_RETRY | CALLING | COMPLETED | FAILED | EXPIRED | SCHEDULE_CONFLICT
stop_reason VARCHAR(100) NULL -- Lý do chi tiết khi kết thúc
);
CREATE INDEX idx_campaign_dispatch ON call_campaign_customer (campaign_id, dispatch_status, call_date, next_attempt_at);
2. API Endpoints
API 1: Lưu cấu hình Lên lịch & Gọi lại (Bước 3)
- Method & Endpoint:
PUT /api/v1/campaigns/callbot/{campaign_id}/schedule-retry-settings - Request Body:
{ "strategy": "scheduled", "campaign_schedule": { "start_date": "2026-10-01", "end_date": "2026-10-15", "time_ranges": [ { "start": "08:30", "end": "11:30" }, { "start": "13:30", "end": "17:30" } ], "days_of_week": [1, 2, 3, 4, 5] }, "retry_settings": { "enabled": true, "conditions": ["busy", "no_answer", "voicemail"], "max_retries": 2, "interval_minutes": 15 }, "continuation_scope": { "type": "next_days", "max_carry_over_days": 2 } } - Response 200 (Success):
{ "status": "success", "message": "Cập nhật cấu hình lịch và gọi lại thành công", "summary_text": "Mỗi khách được gọi tối đa 3 cuộc, cách nhau ít nhất 15 phút. Khách có lịch riêng chỉ được gọi từ giờ đã đặt; có thể chuyển thêm tối đa 2 ngày. Số lần gọi không reset khi chuyển ngày." }
API 2: Import khách hàng kèm lịch riêng (Bước 2)
- Method & Endpoint:
POST /api/v1/campaigns/callbot/{campaign_id}/customers/import - Request Body:
{ "source_type": "file", "file_id": "file_123456", "schedule_mode": "custom_row", "mapping": { "phone": "Số điện thoại", "name": "Họ và tên", "call_date": "Ngày hẹn", "start_time": "Giờ bắt đầu" } } - Response 200 (Success):
{ "total_rows": 100, "valid_rows": 95, "invalid_rows": 5, "conflict_rows": 3, "errors": [ { "row": 12, "phone": "0987654321", "error": "Ngày hẹn không được ở quá khứ" }, { "row": 45, "phone": "0912345678", "error": "Thiếu thông tin ngày gọi" } ] }
API 3: Đặt lại lịch thủ công cho khách hàng đã dừng
- Method & Endpoint:
POST /api/v1/campaigns/callbot/{campaign_id}/customers/{customer_id}/reschedule - Request Body:
{ "call_date": "2026-10-05", "start_time": "09:00", "reset_call_count": false } - Response 200:
{ "status": "success", "customer_id": "cust_987", "dispatch_status": "WAITING", "message": "Đã đặt lại lịch thành công" }
VI. Test Matrix (Chấp thuận nghiệm thu)
Bảng ma trận kiểm thử chuẩn hóa từ 18 kịch bản nghiệp vụ:
| Mã TC | Kịch bản / Điều kiện | Dữ liệu đầu vào | Kỳ vọng hệ thống |
|---|---|---|---|
| TC-01 | Chọn “Gọi ngay”, khách hẹn ngày mai | Chiến dịch: Gọi ngay. Khách A: Ngày mai lúc 09:00. |
Khách A chuyển sang WAITING, không bị gọi ngay lập tức. |
| TC-02 | Khách chỉ nhập ngày, không nhập giờ | Khách B: Ngày 02/10/2026. Chiến dịch: 09:00–11:00 & 14:00–16:00. |
Khách B được gọi ngay từ đầu khung giờ hoạt động hợp lệ đầu tiên (từ 09:00) ở ngày 02/10. |
| TC-03 | Khách hẹn muộn hơn toàn bộ giờ chiến dịch | Khách C: 17:00 ngày hôm nay. Chiến dịch: 09:00–16:00. |
Báo trạng thái SCHEDULE_CONFLICT. Không tự ý gọi lệch giờ. |
| TC-04 | Gọi lại thành công sau khi thất bại | Cuộc 1 thất bại (Máy bận) lúc 09:05. Interval: 15 phút. Giờ bắt đầu khách: 09:00. |
Hệ thống chuyển trạng thái WAITING_RETRY. Cuộc gọi lại sớm nhất từ 09:20. |
| TC-05 | Thất bại gần hết ngày, không kịp interval | Cuộc 1 thất bại lúc 17:25. Chiến dịch kết thúc: 17:30. Phạm vi: Chỉ trong ngày hẹn. |
Không gọi lúc 17:40. Chuyển sang trạng thái FAILED (Lý do: EXPIRED_AFTER_FAILED). |
| TC-06 | Cho tiếp tục trong ngày khi còn ca hoạt động | Khách bắt đầu lúc 09:00. Chiến dịch có 2 ca: 09:00–11:30 và 14:00–17:30. Phạm vi: Trong ngày. |
Cuộc gọi thất bại lúc 11:20 (interval 15 phút) được chuyển sang thử tiếp vào ca chiều từ 14:00. |
| TC-07 | Hết giờ hoạt động trong ngày, không dời ngày | Khách bắt đầu lúc 14:00. Chiến dịch kết thúc lúc 17:30. Phạm vi: Trong ngày. |
Hết giờ chạy chiến dịch trong ngày vẫn chưa kết nối được $\rightarrow$ Dừng xử lý (FAILED, lý do EXPIRED_AFTER_FAILED). |
| TC-08 | Chưa gọi được vì nghẽn kênh, cho chuyển ngày | Chưa gọi được trước khi hết ngày do thiếu kênh SIP. Phạm vi: Cho dời ngày sau. |
Dời sang ngày tiếp theo, giữ nguyên giờ bắt đầu gọi từ 09:00. |
| TC-09 | Tắt gọi lại, cuộc gọi đầu thất bại | Tắt gọi lại. Có bật cho dời ngày sau. | Cuộc gọi đầu thất bại $\rightarrow$ Dừng ngay (FAILED, lý do RETRY_DISABLED_FAILED), không chuyển ngày để gọi lại. |
| TC-10 | Đã thực hiện tối đa số lần gọi lại | Cấu hình: 2 lần gọi lại (tối đa 3 cuộc). Đã gọi 3 cuộc thất bại. Còn hạn chuyển ngày. |
Dừng xử lý (FAILED, lý do MAX_RETRIES_EXCEEDED), không gọi thêm dù còn ngày dời. |
| TC-11 | Chạm hạn mức cuộc gọi trong ngày | Chạm Daily Quota, còn khách có lịch hẹn. Phạm vi: Cho dời ngày. |
Chặn không gọi vượt hạn mức. Dời các khách chưa kịp gọi sang ngày hôm sau. |
| TC-12 | Tạm dừng chiến dịch rồi bật lại sau giờ hoạt động | Khách hẹn 09:00. Pause lúc 09:30, Resume lúc 18:00 (sau khi ca chạy kết thúc 17:30). Phạm vi: Trong ngày. |
Khách chuyển trạng thái EXPIRED (Lý do: CAMPAIGN_ENDED_UNREACHED), không tự động gọi bù ngoài giờ chiến dịch. |
| TC-13 | Cuộc gọi bắt đầu trước giờ kết thúc chiến dịch | Khung ca kết thúc: 17:30. Cuộc gọi bắt đầu lúc 17:29, kết thúc lúc 17:33. |
Cuộc gọi được đàm thoại bình thường cho đến khi hoàn tất. |
| TC-14 | Bắt đầu cuộc gọi tại chính xác mốc kết thúc | Khung ca kết thúc: 17:30. Worker kích hoạt lệnh gọi lúc 17:30:00. |
Chặn không thực hiện cuộc gọi. |
| TC-15 | Nhập lại khách hàng đã có trong chiến dịch | Khách đã có lịch sử gọi 2 cuộc thất bại. Re-import với lịch mới. |
Không tự reset call_count. Cập nhật lịch mới có kiểm soát. |
| TC-16 | Khách lấy lịch từ Contact, sau đó Contact đổi lịch | Đã import thành công vào chiến dịch. Người dùng sửa lịch trên CRM Contact. |
Lịch trong chiến dịch giữ nguyên theo snapshot lúc import, không tự đổi. |
| TC-17 | Danh sách chỉ còn khách hẹn tương lai | Toàn bộ khách hiện tại đã xong hoặc chưa tới giờ. | Chiến dịch ở trạng thái chờ lịch (Waiting for schedule), không coi là Ended. |
| TC-18 | File import có một số dòng sai định dạng | 90 dòng đúng, 10 dòng sai ngày quá khứ hoặc sai giờ. | Báo lỗi 10 dòng sai kèm lý do; import thành công 90 dòng đúng. |