Báo cáo (Reports)
Nhóm 9 báo cáo cho module Telesales: dashboard tổng quan, disposition breakdown, hiệu suất agent, funnel, sức khoẻ caller-ID, heatmap giờ × thứ, lead-source attribution, chuỗi thời gian và abandon rate. Tất cả đều hỗ trợ export CSV/Excel.
Query params chung
Mọi endpoint chia sẻ bộ tham số sau (UTC ISO 8601):
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
campaign_id | integer | optional | Lọc theo chiến dịch | |
date_from | datetime | ✅ | Mốc bắt đầu (ISO 8601 UTC) | |
date_to | datetime | ✅ | Mốc kết thúc (ISO 8601 UTC) | Khoảng date_from..date_to ≤ 90 ngày |
agent_ids[] | array<integer> | optional | Mảng multi-select agent (?agent_ids[]=25&agent_ids[]=26) | |
team_id | integer | optional | Lọc theo nhóm | |
timezone | string | optional | Group theo ngày/giờ dùng timezone này | tên timezone TZDB (UTC, Asia/Ho_Chi_Minh, ...) — default UTC |
format | string | optional | Định dạng response | json (default) / csv / xlsx (xem 9.2) |
Lỗi 422 chung — khoảng vượt 90 ngày:
{
"message": "The given data was invalid.",
"errors": { "date_to": ["The date_from..date_to range may not exceed 90 days."] }
}Tổng quan 9 endpoint
| Endpoint | Nội dung |
|---|---|
GET /api/telesales/reports/dashboard | KPI tổng quan: dials, answer rate, AHT, conversion |
GET /api/telesales/reports/disposition-breakdown | Tỷ lệ phần trăm theo từng disposition code |
GET /api/telesales/reports/agent-performance | KPI per-agent: calls, conversions, AHT, talk time |
GET /api/telesales/reports/funnel | Funnel: Lead → Dialled → Connected → Talked > 30 s → Converted |
GET /api/telesales/reports/caller-id-health | Sức khoẻ caller-ID rotation (ASR, short-call ratio, carrier) |
GET /api/telesales/reports/hour-dow-heatmap | Matrix 24 giờ × 7 ngày (best calling windows) |
GET /api/telesales/reports/lead-source-attribution | Conversion theo nguồn lead |
GET /api/telesales/reports/time-series | Chuỗi thời gian theo ?group_by=day|week|hour |
GET /api/telesales/reports/abandon-rate | Abandon rate realtime (PDPL/TCPA gate) |
9.1 Response mẫu
GET /api/telesales/reports/dashboard?campaign_id=43&date_from=2026-06-01&date_to=2026-06-10
Response fields
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
total_dials | integer | Tổng số lần quay số trong khoảng thời gian | ≥ 0 |
total_answers | integer | Tổng số cuộc gọi được khách hàng bắt máy | ≥ 0 |
answer_rate_pct | string | Chuỗi decimal (2 chữ số) — total_answers / total_dials × 100 | "0.00"-"100.00" |
avg_talk_time_sec | string | Thời gian đàm thoại trung bình (giây) | |
total_talk_time_sec | string | Tổng thời gian đàm thoại (giây) | |
unique_agents | integer | Số agent khác nhau đã quay số trong khoảng | ≥ 0 |
pdpl_blocked_count | integer | Số cuộc gọi bị PDPL chặn (ngoài giờ cho phép) | ≥ 0 |
{
"data": {
"total_dials": 487,
"total_answers": 312,
"answer_rate_pct": "64.07",
"avg_talk_time_sec": "184.5",
"total_talk_time_sec": "57563",
"unique_agents": 12,
"pdpl_blocked_count": 5
}
}GET /api/telesales/reports/agent-performance?campaign_id=43&date_from=...&date_to=...
Response fields (mỗi item trong data[])
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
agent_id | integer | ID user agent | |
name | string | Họ tên agent | |
username | string | Tên đăng nhập | |
dials | integer | Số cuộc đã quay trong khoảng | ≥ 0 |
answers | integer | Số cuộc khách hàng bắt máy | ≥ 0 |
avg_aht_sec | integer | AHT (Average Handle Time) — thời gian xử lý trung bình mỗi cuộc (giây) | ≥ 0 |
conversions | integer | Số cuộc chốt thành công | ≥ 0 |
conv_rate | number | conversions / answers × 100 (float, KHÔNG phải string) | 0-100 |
{
"data": [
{
"agent_id": 3,
"name": "Nguyen Van A",
"username": "agent01",
"dials": 152,
"answers": 98,
"avg_aht_sec": 191,
"conversions": 28,
"conv_rate": 18.42
},
{
"agent_id": 4,
"name": "Tran Thi B",
"username": "agent02",
"dials": 140,
"answers": 86,
"avg_aht_sec": 176,
"conversions": 22,
"conv_rate": 15.71
}
]
}GET /api/telesales/reports/funnel?campaign_id=43&date_from=...&date_to=...
Trả về mảng phẳng (không phải object lồng {stages, conversion_overall_pct}).
Response fields (mỗi item trong data[])
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
stage | string | Tên bước trong funnel | Total leads / Dialed / Connected / Talked > 30s / Disposed / Converted |
count | integer | Số lead/cuộc gọi ở bước này | ≥ 0 |
{
"data": [
{ "stage": "Total leads", "count": 487 },
{ "stage": "Dialed", "count": 312 },
{ "stage": "Connected", "count": 248 },
{ "stage": "Talked > 30s", "count": 198 },
{ "stage": "Disposed", "count": 290 },
{ "stage": "Converted", "count": 78 }
]
}Có 6 stage — gồm cả Disposed. Client tự tính conversion_overall_pct = Converted / Total_leads × 100 từ stage đầu và cuối.
GET /api/telesales/reports/disposition-breakdown?campaign_id=43&date_from=...&date_to=...
Response fields (mỗi item trong data[])
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
code | string | Mã disposition | |
label | string | Nhãn hiển thị | |
category | string | Nhóm kết quả | contact / no_contact / callback / remove |
color | string | Mã màu UI badge | #RRGGBB |
count | integer | Số cuộc gọi có disposition này | ≥ 0 |
pct | string | Chuỗi decimal 2 chữ số — phần trăm trên tổng cuộc gọi | "0.00"-"100.00" |
{
"data": [
{
"code": "no_answer",
"label": "No Answer",
"category": "no_contact",
"color": "#6B7280",
"count": 95,
"pct": "30.45"
},
{
"code": "sale",
"label": "Sale Success",
"category": "contact",
"color": "#10B981",
"count": 78,
"pct": "25.00"
}
]
}Mỗi item kèm category + color (UI dùng render badge). pct là chuỗi decimal. Không có wrapper meta: {total_calls} — client tự tính total = sum(count).
GET /api/telesales/reports/caller-id-health?date_from=...&date_to=...
Response fields (mỗi item trong data[])
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
cid | string | Số caller-ID | |
display_name | string|null | Tên nội bộ | |
carrier | string | Nhà mạng | viettel / mobifone / vinaphone / vietnamobile / landline / other |
cli_type | string | Loại số | mobile / fixed_02x / hotline_1900 / hotline_1800 / brandname |
pool_status | string | Trạng thái trong rotation pool | active / cooldown / paused / disabled |
cooldown_until | datetime|null | Lúc hết cooldown | ISO 8601 UTC |
daily_cap | integer | Giới hạn cuộc/ngày | ≥ 1 |
total_calls | integer | Tổng số cuộc đã gọi trong khoảng | ≥ 0 |
answered_calls | integer | Số cuộc khách hàng bắt máy | ≥ 0 |
asr_pct | number | Answer-Seizure Ratio (%) — answered/total × 100 | 0-100 |
acd_sec | integer | Average Call Duration — thời lượng đàm thoại trung bình (giây) | ≥ 0 |
short_calls | integer | Số cuộc dưới ngưỡng "đàm thoại có ý nghĩa" | ≥ 0 |
short_call_ratio_pct | number | Tỷ lệ cuộc ngắn / tổng | 0-100 |
rejected_calls | integer | Số cuộc bị từ chối | ≥ 0 |
health_status | string | Phân loại sức khoẻ | healthy / warning / suspected_block / no_data |
health_calls_today | integer | Số cuộc gọi hôm nay (dùng đánh giá cooldown) | ≥ 0 |
{
"data": [
{
"cid": "0900000012",
"display_name": "Sales Line 1",
"carrier": "viettel",
"cli_type": "fixed_02x",
"pool_status": "active",
"cooldown_until": null,
"daily_cap": 200,
"total_calls": 152,
"answered_calls": 98,
"asr_pct": 64.5,
"acd_sec": 121,
"short_calls": 28,
"short_call_ratio_pct": 18.4,
"rejected_calls": 2,
"health_status": "healthy",
"health_calls_today": 87
}
]
}GET /api/telesales/reports/hour-dow-heatmap?campaign_id=43&date_from=...&date_to=...
Trả về mảng phẳng 168 phần tử (7 ngày × 24 giờ), không lồng theo day-of-week.
Response fields (mỗi item trong data[])
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
day_of_week | integer | Thứ trong tuần | 0 = CN, 1 = T2, ..., 6 = T7 |
hour | integer | Giờ trong ngày (theo timezone query param) | 0-23 |
dials | integer | Số cuộc đã quay trong slot | ≥ 0 |
answered | integer | Số cuộc khách bắt máy | ≥ 0 |
answer_rate_pct | number | Tỷ lệ bắt máy answered/dials × 100 | 0-100 |
{
"data": [
{ "day_of_week": 1, "hour": 0, "dials": 0, "answered": 0, "answer_rate_pct": 0 },
{ "day_of_week": 1, "hour": 1, "dials": 0, "answered": 0, "answer_rate_pct": 0 },
{ "day_of_week": 1, "hour": 9, "dials": 87, "answered": 56, "answer_rate_pct": 64.4 }
]
}Client pivot thành matrix 7 × 24 để render heatmap.
GET /api/telesales/reports/lead-source-attribution?date_from=...&date_to=...
Response fields (mỗi item trong data[])
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
source | string | Nguồn lead — lead không có nguồn gom dưới "(unknown)" | |
total_leads | integer | Tổng số lead từ nguồn này trong khoảng | ≥ 0 |
dialed_count | integer | Số lead đã được quay số | ≥ 0 |
contacted_count | integer | Số lead khách hàng bắt máy | ≥ 0 |
conv_count | integer | Số lead chốt thành công | ≥ 0 |
conv_rate_pct | string | Tỷ lệ chuyển đổi conv_count/total_leads × 100 (decimal 2 chữ số) | "0.00"-"100.00" |
{
"data": [
{
"source": "facebook_lead_ads",
"total_leads": 240,
"dialed_count": 230,
"contacted_count": 152,
"conv_count": 62,
"conv_rate_pct": "25.83"
},
{
"source": "(unknown)",
"total_leads": 5,
"dialed_count": 3,
"contacted_count": 2,
"conv_count": 0,
"conv_rate_pct": "0.00"
}
]
}GET /api/telesales/reports/time-series?campaign_id=43&group_by=day&date_from=...&date_to=...
Trả về mảng phẳng — không có wrapper {group_by, metrics, series}.
Query parameter bổ sung
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
group_by | string | optional | Bước nhóm thời gian | day (default) / week / hour |
Response fields (mỗi item trong data[])
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
bucket | string | Mốc thời gian — format theo group_by | day → YYYY-MM-DD; week → YYYY-Www (ISO 8601); hour → YYYY-MM-DD HH:00:00 |
dials | integer | Số cuộc đã quay trong bucket | ≥ 0 |
answers | integer | Số cuộc khách bắt máy | ≥ 0 |
talk_sec | integer | Tổng thời gian đàm thoại (giây) | ≥ 0 |
conversions | integer | Số cuộc chốt thành công | ≥ 0 |
{
"data": [
{ "bucket": "2026-06-01", "dials": 110, "answers": 68, "talk_sec": 9180, "conversions": 18 },
{ "bucket": "2026-06-02", "dials": 105, "answers": 71, "talk_sec": 10260, "conversions": 16 },
{ "bucket": "2026-06-05", "dials": 82, "answers": 55, "talk_sec": 6720, "conversions": 15 }
]
}GET /api/telesales/reports/abandon-rate?campaign_id=43 (realtime)
Response của endpoint này phong phú hơn các báo cáo khác — chứa current (snapshot live) + breaches (lịch sử lần vượt ngưỡng gần đây).
Response fields
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
current.campaign_id | integer | ID chiến dịch | |
current.attempts_total | integer | Tổng số attempt trong cửa sổ đo | ≥ 0 |
current.attempts_abandoned | integer | Số attempt bị abandon trước khi agent kịp nói | ≥ 0 |
current.abandon_rate_pct | number | Tỷ lệ abandon (%) | 0-100 |
current.window_minutes | integer | Độ dài cửa sổ đo (phút, default 30 ngày = 43.200) | ≥ 1 |
current.window_from | datetime | Mốc bắt đầu cửa sổ | ISO 8601 |
current.window_to | datetime | Mốc kết thúc cửa sổ | ISO 8601 |
current.campaign_name | string | Tên chiến dịch | |
current.campaign_status | string | Trạng thái chiến dịch | draft / active / paused / completed |
current.limit_pct | number | Ngưỡng PDPL/TCPA cho phép | default 3 |
current.breached | bool | Đang vượt ngưỡng hay không | |
breaches[] | array | Lịch sử các snapshot đã vượt ngưỡng (rỗng nếu chưa từng vượt) |
{
"data": {
"current": {
"campaign_id": 43,
"attempts_total": 1,
"attempts_abandoned": 0,
"abandon_rate_pct": 0,
"window_minutes": 43200,
"window_from": "2026-05-10 02:31:14",
"window_to": "2026-06-09 02:31:14",
"tenant_id": 1,
"campaign_name": "Outbound Sales Q3-2026 03",
"campaign_status": "active",
"limit_pct": 3,
"breached": false
},
"breaches": []
}
}Khi breached=true, mảng breaches chứa lịch sử snapshot (các cửa sổ thời gian đã vượt giới hạn). Client dùng để render banner cảnh báo và vẽ trend.
9.2 Export CSV / Excel
GET /api/telesales/reports/{report}/export?format=csv|xlsx{report} ∈ {dashboard, disposition-breakdown, agent-performance, funnel, caller-id-health, hour-dow-heatmap, lead-source-attribution, time-series}.
Response: file stream (Content-Type: text/csv hoặc application/vnd.openxmlformats-officedocument.spreadsheetml.sheet).
Layout export mặc định là chuẩn chung. Template Excel theo từng khách hàng (thứ tự cột, label header, conditional formatting riêng) được cấu hình trong gói cấu hình riêng.
