1、修复后台首页数据概览的团队积分消耗排行情况
2、修复前台团队的退款统计问题
This commit is contained in:
@@ -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_internal:amount 是负数(扣减积分),统计用 abs() 保证为正值
|
||||||
|
# - refund:amount 是正数(退回积分),但为兼容旧数据/边缘场景也用 abs() 保证统计值恒正
|
||||||
|
# 避免前台筛选"退款"类型时总退款显示为 0/负数导致用户误以为没有统计
|
||||||
|
# - recharge:amount 是正数(充值增加),金额直接求和,不需要 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)),
|
||||||
|
|||||||
Reference in New Issue
Block a user