1、修复后台首页数据概览的团队积分消耗排行情况

2、修复前台团队的退款统计问题
This commit is contained in:
2026-08-05 10:17:12 +08:00
parent 5de637ab6b
commit ceb930bd5a
2 changed files with 21 additions and 21 deletions
+15 -20
View File
@@ -2,7 +2,7 @@ from datetime import datetime, timezone, timedelta
import json import json
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import case, delete, func, or_, select, update from sqlalchemy import and_, case, delete, func, or_, select, update
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_db, get_admin_user from app.dependencies import get_db, get_admin_user
@@ -1963,36 +1963,31 @@ async def get_stats(
] ]
# ── 各团队积分消耗(有团队 vs 无团队,使用流水中的团队快照) # ── 各团队积分消耗(有团队 vs 无团队,使用流水中的团队快照)
# 消耗 = 扣除金额 - 退回金额(净消耗 # 消耗 = 实际消费(charge_action IS NULL 或 charge - 退款(charge_action=refund 不限制
# 注意:real_credit_charge_filter 仅应限制 consume 类型(预扣 hold 释放不算真实消费);
# refund 类型的 charge_action = 'refund',不能套用同一个 filter
# 所以把该 filter 下移到 CASE WHEN 内而不是写在 WHERE 中,避免退款被误过滤。
_consume_filter = and_(
CreditRecord.type == "consume",
real_credit_charge_filter,
)
_consume_expr = case((_consume_filter, func.abs(CreditRecord.amount)), else_=0)
_refund_expr = case((CreditRecord.type == "refund", func.abs(CreditRecord.amount)), else_=0)
team_credit_rows = (await db.execute( team_credit_rows = (await db.execute(
select( select(
func.coalesce(CreditRecord.team_name_snapshot, '未分配团队').label('team_name'), func.coalesce(CreditRecord.team_name_snapshot, '未分配团队').label('team_name'),
CreditRecord.team_id_snapshot.label('team_id'), CreditRecord.team_id_snapshot.label('team_id'),
func.coalesce( func.coalesce(func.sum(_consume_expr), 0).label("total_consume"),
func.sum( func.coalesce(func.sum(_refund_expr), 0).label("total_refund"),
case(
(CreditRecord.type == "consume", func.abs(CreditRecord.amount)),
else_=0,
)
), 0
).label("total_consume"),
func.coalesce(
func.sum(
case(
(CreditRecord.type == "refund", func.abs(CreditRecord.amount)),
else_=0,
)
), 0
).label("total_refund"),
) )
.where( .where(
CreditRecord.type.in_(["consume", "refund"]), CreditRecord.type.in_(["consume", "refund"]),
real_credit_charge_filter,
CreditRecord.created_at >= date_start, CreditRecord.created_at >= date_start,
CreditRecord.created_at <= date_end, CreditRecord.created_at <= date_end,
) )
.group_by(CreditRecord.team_id_snapshot, CreditRecord.team_name_snapshot) .group_by(CreditRecord.team_id_snapshot, CreditRecord.team_name_snapshot)
.order_by(func.coalesce(func.sum(case((CreditRecord.type == "consume", func.abs(CreditRecord.amount)), else_=0)), 0).desc()) # 按"净消耗 = 总消费 - 总退款"倒序排序(排行榜)
.order_by((func.coalesce(func.sum(_consume_expr), 0) - func.coalesce(func.sum(_refund_expr), 0)).desc())
)).all() )).all()
credits_by_team = [ credits_by_team = [
TeamCreditOut( TeamCreditOut(
@@ -295,10 +295,15 @@ async def list_admin_credit_records(
deleted_map = await _load_deleted_map(db, records) deleted_map = await _load_deleted_map(db, records)
items = [_record_to_item(record, user, deleted_map) for record, user in rows] items = [_record_to_item(record, user, deleted_map) for record, user in rows]
# 说明:
# - consume / team_internalamount 是负数(扣减积分),统计用 abs() 保证为正值
# - refundamount 是正数(退回积分),但为兼容旧数据/边缘场景也用 abs() 保证统计值恒正
# 避免前台筛选"退款"类型时总退款显示为 0/负数导致用户误以为没有统计
# - rechargeamount 是正数(充值增加),金额直接求和,不需要 abs
summary_query = select( summary_query = select(
func.coalesce(func.sum(case((CreditRecord.type == "recharge", CreditRecord.amount), else_=0)), 0), func.coalesce(func.sum(case((CreditRecord.type == "recharge", CreditRecord.amount), else_=0)), 0),
func.coalesce(func.sum(case((and_(CreditRecord.type.in_(["consume", "team_internal"]), (or_(CreditRecord.charge_action.is_(None), CreditRecord.charge_action == "charge"))), func.abs(CreditRecord.amount)), else_=0)), 0), func.coalesce(func.sum(case((and_(CreditRecord.type.in_(["consume", "team_internal"]), (or_(CreditRecord.charge_action.is_(None), CreditRecord.charge_action == "charge"))), func.abs(CreditRecord.amount)), else_=0)), 0),
func.coalesce(func.sum(case((CreditRecord.type == "refund", CreditRecord.amount), else_=0)), 0), func.coalesce(func.sum(case((CreditRecord.type == "refund", func.abs(CreditRecord.amount)), else_=0)), 0),
func.count(CreditRecord.id), func.count(CreditRecord.id),
func.count(distinct(case((and_(CreditRecord.credit_subject == CreditRecordSubject.MEDIA.value, CreditRecord.type == "consume", (or_(CreditRecord.charge_action.is_(None), CreditRecord.charge_action == "charge"))), func.concat(CreditRecord.owner_type, ":", CreditRecord.owner_id)), else_=None))), func.count(distinct(case((and_(CreditRecord.credit_subject == CreditRecordSubject.MEDIA.value, CreditRecord.type == "consume", (or_(CreditRecord.charge_action.is_(None), CreditRecord.charge_action == "charge"))), func.concat(CreditRecord.owner_type, ":", CreditRecord.owner_id)), else_=None))),
func.count(case((and_(CreditRecord.credit_subject == CreditRecordSubject.MEDIA.value, CreditRecord.type == "consume", (or_(CreditRecord.charge_action.is_(None), CreditRecord.charge_action == "charge"))), 1), else_=None)), func.count(case((and_(CreditRecord.credit_subject == CreditRecordSubject.MEDIA.value, CreditRecord.type == "consume", (or_(CreditRecord.charge_action.is_(None), CreditRecord.charge_action == "charge"))), 1), else_=None)),