diff --git a/app/admin/repositories/queries.py b/app/admin/repositories/queries.py index 4e7f2d2..9c05600 100644 --- a/app/admin/repositories/queries.py +++ b/app/admin/repositories/queries.py @@ -493,15 +493,19 @@ def withdraw_risk_flags( created_at = user.created_at.replace(tzinfo=timezone.utc) if user.created_at.tzinfo is None else user.created_at if datetime.now(timezone.utc) - created_at < timedelta(hours=24): flags.append("新注册用户") - failed_or_rejected = sum(1 for item in recent_withdraws if item.status in {"failed", "rejected"}) - if failed_or_rejected: - flags.append(f"历史异常提现{failed_or_rejected}笔") + # 历史异常提现拆「拒绝」「失败」两类(口径不同:拒绝=人工驳回退款,失败=打款失败退款) + rejected_n = sum(1 for item in recent_withdraws if item.status == "rejected") + failed_n = sum(1 for item in recent_withdraws if item.status == "failed") + if rejected_n: + flags.append(f"提现拒绝{rejected_n}笔") + if failed_n: + flags.append(f"提现失败{failed_n}笔") recent_reviewing = sum(1 for item in recent_withdraws if item.status == "reviewing") if recent_reviewing >= 3: flags.append(f"待审核提现偏多:{recent_reviewing}笔") if cash_balance_cents < 0: flags.append("现金余额为负") - score = min(100, len(flags) * 20 + failed_or_rejected * 10) + score = min(100, len(flags) * 20 + (rejected_n + failed_n) * 10) return flags, score @@ -688,8 +692,8 @@ def user_coin_records( date_to: datetime | None = None, limit: int = 20, cursor: int | None = None, -) -> tuple[list[dict], int | None]: - """金币发放记录(提现详情底部表),按时间倒序、offset 游标分页。 +) -> tuple[list[dict], int | None, int]: + """金币发放记录(提现详情底部表),按时间倒序、offset 游标分页 + 总数。 合并三类来源(Phase1 信息流不分比价/领券): - 激励视频 / 签到膨胀 = ad_reward_record(granted) @@ -758,7 +762,29 @@ def user_coin_records( rows.sort(key=lambda r: r["created_at"], reverse=True) has_more = len(rows) > offset + limit - return rows[offset:offset + limit], (offset + limit if has_more else None) + + # 总数 = 三源在窗口内 granted 计数之和(供前端页码分页渲染页码/共 N 条) + def _count(model, *conds) -> int: + return int(db.execute(select(func.count()).select_from(model).where(*conds)).scalar_one()) + + total = ( + _count( + AdRewardRecord, AdRewardRecord.user_id == user_id, + AdRewardRecord.status == "granted", + *_window_conds(AdRewardRecord.created_at, date_from, date_to), + ) + + _count( + AdFeedRewardRecord, AdFeedRewardRecord.user_id == user_id, + AdFeedRewardRecord.status == "granted", + *_window_conds(AdFeedRewardRecord.created_at, date_from, date_to), + ) + + _count( + CoinTransaction, CoinTransaction.user_id == user_id, + CoinTransaction.biz_type == "signin", + *_window_conds(CoinTransaction.created_at, date_from, date_to), + ) + ) + return rows[offset:offset + limit], (offset + limit if has_more else None), total def list_price_reports( diff --git a/app/admin/routers/users.py b/app/admin/routers/users.py index 2e0a017..4b98bea 100644 --- a/app/admin/routers/users.py +++ b/app/admin/routers/users.py @@ -100,11 +100,11 @@ def get_user_coin_records( limit: Annotated[int, Query(ge=1, le=100)] = 20, cursor: Annotated[int | None, Query()] = None, ) -> CursorPage[UserCoinRecord]: - items, next_cursor = queries.user_coin_records( + items, next_cursor, total = queries.user_coin_records( db, user_id, date_from=date_from, date_to=date_to, limit=limit, cursor=cursor, ) return CursorPage( - items=[UserCoinRecord(**r) for r in items], next_cursor=next_cursor, + items=[UserCoinRecord(**r) for r in items], next_cursor=next_cursor, total=total, )