From f88f4a4e135fdba0f8eabb5c103d7764e98c65b0 Mon Sep 17 00:00:00 2001 From: Pine Date: Fri, 11 Sep 2026 21:54:01 +0800 Subject: [PATCH] =?UTF-8?q?fix(enterprise):=20=E4=BC=81=E4=B8=9A=E6=88=90?= =?UTF-8?q?=E5=91=98=E5=88=97=E8=A1=A8=E7=AE=97=E5=8A=9B=E4=BD=99=E9=A2=9D?= =?UTF-8?q?=E6=94=B9=E8=AF=BB=20compute-engine=20=E5=AE=9E=E6=97=B6?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=E4=BD=99=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit company_members.compute_balance 仅含企业分配,个人充值不进入该字段, 导致企业管理看不到成员真实算力。改为并发查询引擎 users.quota(微元, 个人充值+企业分配全量计入),换算为分返回;引擎不可用时回退平台镜像。 --- app/park/tenants.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/app/park/tenants.py b/app/park/tenants.py index fd812ec..0d64661 100644 --- a/app/park/tenants.py +++ b/app/park/tenants.py @@ -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 users;is_admin 标注企业管理员)。""" + """企业成员列表(company_members JOIN users;is_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: