feat(calls): 微信语音列表加今日统计卡片+通话时间范围筛选+客户跳转带通话时间定位(对齐云客)

- GET /api/calls/stats:今日(Asia/Shanghai)好友呼入/外呼量+群量+时长聚合(@chatroom判群)
- CallList:5张统计卡片(仅微信语音tab)+datetimerange筛选(服务端start/end)+viewCallCustomer带?start定位
- TDD新增stats聚合测试,全量绿

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-20 11:08:13 +08:00
parent 6c9245a682
commit 4aeed003a4
6 changed files with 188 additions and 12 deletions
+34
View File
@@ -347,3 +347,37 @@ async def test_scope_invalid_value_rejected(make_client):
client = await make_client(_seed_voice_scopes)
r = await client.get("/api/calls", params={"scope": "bogus"})
assert r.status_code == 422
# ---------------------------------------------------------------------------
# GET /api/calls/stats — 微信语音列表顶部统计卡片(今日,对齐云客)
# ---------------------------------------------------------------------------
async def _seed_voice_stats(session: AsyncSession) -> None:
"""今日(按 Asia/Shanghai)语音:好友呼入2/呼出1/群1,呼出接通时长60s。"""
import datetime as dt
# today_start 计算依赖时区,直接用一个肯定在"今天"的远未来时间戳构造会跨天;
# 改用相对 now 的 ms —— 种子用比 now 晚 1ms 保证落进今天窗口。
soon = int(dt.datetime.now(dt.UTC).timestamp() * 1000) + 1000
session.add_all([
PhoneCall(call_id="s1", channel="voice_call", start_call_time=soon, incoming_call=1, call_duration=30, call_number="q1"),
PhoneCall(call_id="s2", channel="voice_call", start_call_time=soon, incoming_call=1, call_duration=10, call_number="q2"),
PhoneCall(call_id="s3", channel="voice_call", start_call_time=soon, incoming_call=0, call_duration=60, call_number="q3"),
PhoneCall(call_id="s4", channel="voice_call", start_call_time=soon, incoming_call=1, call_duration=5, call_number="r@chatroom"),
PhoneCall(call_id="s5_old", channel="voice_call", start_call_time=1000, incoming_call=1, call_duration=99, call_number="q_old"),
])
await session.commit()
async def test_calls_stats_today_aggregation(make_client):
client = await make_client(_seed_voice_stats)
r = await client.get("/api/calls/stats")
assert r.status_code == 200
s = r.json()
assert s["friend_incoming"] == 2
assert s["friend_outgoing"] == 1
assert s["group_count"] == 1
assert s["friend_outgoing_duration"] == 60
assert s["friend_incoming_duration"] == 40
assert s["group_duration"] == 5