feat(customers): 发送/接收列改今日口径(对齐云客;本地消息表Asia/Shanghai今日聚合)

- CustomerOut 加 send_today/receive_today(路由赋值,非ORM列)
- /api/customers 每页一次 GROUP BY(owner IN 走复合索引前缀)
- 前端列优先今日值,无消息数据回退原raw字段;列名对齐云客「发送条数/接收条数」
- TDD:今日3发2收+昨日不计入+无消息客户0

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-22 14:39:17 +08:00
parent 00ae13b5c2
commit a290e39523
5 changed files with 88 additions and 5 deletions
+39 -1
View File
@@ -112,5 +112,43 @@ async def list_customers(
)
rows = (await session.execute(list_stmt)).scalars().all()
items = [CustomerOut.model_validate(row) for row in rows]
# 今日发送/接收条数(对齐云客口径):本地消息表按 Asia/Shanghai 今日聚合,
# 一次 GROUP BY 覆盖本页(owner IN 走 ix_message_channel_owner_talker_time 前缀)
import datetime as _dt
from app.db.models import Message
sh_now = _dt.datetime.now(_dt.UTC) + _dt.timedelta(hours=8)
today_start_ms = int(
(sh_now.replace(hour=0, minute=0, second=0, microsecond=0) - _dt.timedelta(hours=8)).timestamp() * 1000
)
owners = sorted({r.owner_account_id for r in rows if r.owner_account_id})
today_counts: dict[tuple[str, str], tuple[int, int]] = {}
if owners:
stat_rows = (
await session.execute(
sa.select(
Message.owner_account_id,
Message.talker_id,
sa.func.sum(sa.case((Message.is_send.is_(True), 1), else_=0)),
sa.func.sum(sa.case((Message.is_send.is_(False), 1), else_=0)),
)
.where(
Message.channel == (channel or "wechat"),
Message.owner_account_id.in_(owners),
Message.timestamp >= today_start_ms,
)
.group_by(Message.owner_account_id, Message.talker_id)
)
).all()
today_counts = {(o, t): (int(s or 0), int(rc or 0)) for o, t, s, rc in stat_rows}
items = []
for row in rows:
item = CustomerOut.model_validate(row)
s, rc = today_counts.get((row.owner_account_id or "", row.friend_id or ""), (0, 0))
item.send_today = s
item.receive_today = rc
items.append(item)
return Page(items=items, total=total, page=page, page_size=page_size)
+4
View File
@@ -25,6 +25,10 @@ class CustomerOut(BaseModel):
channel: str
owner_account_id: str | None = None
friend_nickname: str | None = None
# 今日发送/接收条数(本地消息表按 Asia/Shanghai 今日聚合,对齐云客口径;
# 非 ORM 列,由路由赋值,2026-08-22)
send_today: int | None = None
receive_today: int | None = None
friend_remark: str | None = None
real_name: str | None = None
phone: str | None = None