fix(enterprise): 企业成员列表算力余额改读 compute-engine 实时真实余额

company_members.compute_balance 仅含企业分配,个人充值不进入该字段,
导致企业管理看不到成员真实算力。改为并发查询引擎 users.quota(微元,
个人充值+企业分配全量计入),换算为分返回;引擎不可用时回退平台镜像。
This commit is contained in:
Pine
2026-09-11 21:54:01 +08:00
parent 416ade4842
commit f88f4a4e13
+34 -2
View File
@@ -7,6 +7,7 @@ sim_engine 需同步 tick,故另提供同步只读访问器 get_tenant_data_sy
"""
from __future__ import annotations
import asyncio
from datetime import datetime
import hashlib
@@ -491,15 +492,22 @@ async def company_member_count(s, company_id: str) -> int:
async def company_members(company_id: str) -> list[dict]:
"""企业成员列表(company_members JOIN usersis_admin 标注企业管理员)。"""
"""企业成员列表(company_members JOIN usersis_admin 标注企业管理员)。
算力余额口径:compute_balance / compute_balance_used 采用 compute-engine 实时
真实余额(单位=分;引擎 quota 为微元,个人充值+企业分配全部计入),
避免只显示企业分配余额(company_members.compute_balance)而漏掉个人充值部分;
引擎不可用时回退平台镜像(users.compute_quota / company_members.compute_balance)。
"""
async with _get_session() as s:
rows = (await s.execute(
select(User, CompanyMember).join(CompanyMember, CompanyMember.user_id == User.id).where(
CompanyMember.company_id == company_id, CompanyMember.status == "active"
).order_by(CompanyMember.created_at)
)).all()
return [{
items = [{
"id": u.id, "username": u.username, "nickname": u.nickname or "",
"avatar": u.avatar or "",
"phone": u.phone or "", "role": u.role or "",
"is_admin": bool(cm.is_admin), "member_type": cm.member_type,
"certification_status": u.certification_status or "uncertified",
@@ -512,6 +520,30 @@ async def company_members(company_id: str) -> list[dict]:
"created_at": u.created_at or "",
} for u, cm in rows]
# 并发刷新引擎真实余额(1 元 = 1_000_000 micro = 100 分 → 1 分 = 10_000 micro
if items:
from ..services import compute_client # 延迟导入避免循环依赖
async def _engine_balance(uname: str):
try:
data = await compute_client.user_balance(uname)
quota = int(data.get("quota") or 0)
used = int(data.get("used_quota") or 0)
return quota, used
except Exception: # noqa: BLE001 引擎不可用时回退镜像
return None
balances = await asyncio.gather(*(_engine_balance(it["username"]) for it in items))
for it, bal in zip(items, balances):
if bal is None:
continue
quota, used = bal
it["compute_quota"] = quota
it["compute_used_quota"] = used
it["compute_balance"] = max(0, quota - used) // 10_000 # 可用余额(分)
it["compute_balance_used"] = used // 10_000 # 已用(分)
return items
async def add_company_member(company_id: str, user_id: str, *, is_admin: bool = False,
created_by: str = "") -> dict | None: