1、修改后台首页团队消耗没有算回退的数据
2、修改前台团队积分不可以下载回退积分的数据
This commit is contained in:
@@ -1948,23 +1948,43 @@ async def get_stats(
|
||||
]
|
||||
|
||||
# ── 各团队积分消耗(有团队 vs 无团队,使用流水中的团队快照)
|
||||
# 消耗 = 扣除金额 - 退回金额(净消耗)
|
||||
team_credit_rows = (await db.execute(
|
||||
select(
|
||||
func.coalesce(CreditRecord.team_name_snapshot, '未分配团队').label('team_name'),
|
||||
CreditRecord.team_id_snapshot.label('team_id'),
|
||||
func.coalesce(func.sum(func.abs(CreditRecord.amount)), 0).label('credits'),
|
||||
func.coalesce(
|
||||
func.sum(
|
||||
func.case(
|
||||
(CreditRecord.type == "consume", func.abs(CreditRecord.amount)),
|
||||
else_=0,
|
||||
)
|
||||
), 0
|
||||
).label("total_consume"),
|
||||
func.coalesce(
|
||||
func.sum(
|
||||
func.case(
|
||||
(CreditRecord.type == "refund", func.abs(CreditRecord.amount)),
|
||||
else_=0,
|
||||
)
|
||||
), 0
|
||||
).label("total_refund"),
|
||||
)
|
||||
.where(
|
||||
CreditRecord.type == "consume",
|
||||
CreditRecord.type.in_(["consume", "refund"]),
|
||||
real_credit_charge_filter,
|
||||
CreditRecord.created_at >= date_start,
|
||||
CreditRecord.created_at <= date_end,
|
||||
)
|
||||
.group_by(CreditRecord.team_id_snapshot, CreditRecord.team_name_snapshot)
|
||||
.order_by(func.coalesce(func.sum(func.abs(CreditRecord.amount)), 0).desc())
|
||||
.order_by(func.coalesce(func.sum(func.case((CreditRecord.type == "consume", func.abs(CreditRecord.amount)), else_=0)), 0).desc())
|
||||
)).all()
|
||||
credits_by_team = [
|
||||
TeamCreditOut(team_name=row.team_name, team_id=row.team_id, credits=float(row.credits or 0))
|
||||
TeamCreditOut(
|
||||
team_name=row.team_name,
|
||||
team_id=row.team_id,
|
||||
credits=round(float(row.total_consume or 0) - float(row.total_refund or 0), 2),
|
||||
)
|
||||
for row in team_credit_rows
|
||||
]
|
||||
|
||||
|
||||
@@ -379,15 +379,41 @@ async def export_team_credit_records(
|
||||
end_date=end_date,
|
||||
)
|
||||
|
||||
# 生成 CSV(兼容 Excel 打开)
|
||||
# 生成 CSV(兼容 Excel 打开,UTF-8 BOM)
|
||||
import csv
|
||||
import io
|
||||
from datetime import datetime as _dt
|
||||
|
||||
def _format_dt(val):
|
||||
if val is None:
|
||||
return "-"
|
||||
|
||||
return str(datetime.fromtimestamp(val).strftime("%Y-%m-%d %H:%M:%S"))
|
||||
try:
|
||||
# 情况 1:已经是 datetime
|
||||
if isinstance(val, _dt):
|
||||
dt = val
|
||||
elif isinstance(val, (int, float)):
|
||||
# 情况 2:Unix 时间戳(极少,兼容旧代码)
|
||||
dt = _dt.fromtimestamp(val)
|
||||
elif isinstance(val, str):
|
||||
# 情况 3:ISO 字符串(admin_credit_record_service._iso 返回的格式)
|
||||
s = val.strip()
|
||||
if s.endswith("Z"):
|
||||
s = s[:-1] + "+00:00"
|
||||
try:
|
||||
dt = _dt.fromisoformat(s)
|
||||
except ValueError:
|
||||
# 兼容旧格式 YYYY-MM-DD HH:MM:SS
|
||||
dt = _dt.strptime(s, "%Y-%m-%d %H:%M:%S")
|
||||
else:
|
||||
return str(val)
|
||||
# 统一转东八区展示
|
||||
if getattr(dt, "tzinfo", None) is None:
|
||||
dt = dt.replace(tzinfo=CST)
|
||||
else:
|
||||
dt = dt.astimezone(CST)
|
||||
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
except Exception: # noqa: BLE001
|
||||
return str(val) if val else "-"
|
||||
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output)
|
||||
|
||||
@@ -59,7 +59,9 @@ def _as_date_start(value: str | None) -> datetime | None:
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
return datetime.strptime(value, "%Y-%m-%d")
|
||||
# 构造东八区 00:00:00 与 DB timezone-aware created_at 比较,避免 8 小时偏移
|
||||
naive = datetime.strptime(value, "%Y-%m-%d")
|
||||
return naive.replace(tzinfo=CST)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@@ -68,7 +70,11 @@ def _as_date_end(value: str | None) -> datetime | None:
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
return datetime.strptime(value, "%Y-%m-%d").replace(hour=23, minute=59, second=59, microsecond=999999)
|
||||
# 构造东八区 23:59:59.999999
|
||||
naive = datetime.strptime(value, "%Y-%m-%d").replace(
|
||||
hour=23, minute=59, second=59, microsecond=999999,
|
||||
)
|
||||
return naive.replace(tzinfo=CST)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@@ -250,7 +256,9 @@ async def list_admin_credit_records(
|
||||
end_date: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
page = max(int(page or 1), 1)
|
||||
page_size = min(max(int(page_size or 20), 1), 1000)
|
||||
# 列表页默认最多 1000 条;导出接口可传较大值(最多 100000 条),避免月度导出被截断
|
||||
max_page_size = 100000 if page_size is not None and int(page_size) > 1000 else 1000
|
||||
page_size = min(max(int(page_size or 20), 1), max_page_size)
|
||||
filters = _build_filters(
|
||||
user_id=user_id,
|
||||
user_name=user_name,
|
||||
|
||||
Reference in New Issue
Block a user