feat(park-company): 园区企业→成员 + 企业算力(折扣%/配额,只增不减) 后端
- 迁移0015:park_companies 加 compute_discount/compute_quota/compute_quota_used/engine_group/owner_user_id;users 加 park_company_id(成员归属一家企业)
- tenants.py:company_members/get/add/remove(写 users.park_company_id)、available_members(候选)、set_company_compute(折扣%/配额 单调非降,越界 ValueError)、sync_company_engine(引擎组倍率同步,best-effort)
- park/routers.py:carrier 端点 company-members GET/POST/DELETE、company-pool、companies/{cid}/compute(monotonic+引擎同步);鉴权 _carrier_user+_my_park 隔离本园区
- compute_client.py:set_user_group_by_username、set_group_group_ratio(组倍率=1-discount%)、grant_user_quota_by_username(解析引擎id后 add_quota)
- rbac_admin.py:平台兜底 GET /park/tenants/{tid}/companies、PUT .../companies/{cid}/compute(monotonic+audit)
- _user_to_dict 暴露 park_company_id
- 单调校验服务端强判:折扣降/配额负增 → 400
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..dependencies import get_db
|
||||
from ..schemas.admin import SetUserRoleRequest, SetUserStatusRequest, UserClassificationRequest
|
||||
@@ -456,3 +456,49 @@ async def admin_park_unbind_device(
|
||||
await write_audit(db, action="park.unbind_screen", resource="park_screen",
|
||||
resource_id=device_id, detail=f"from tid={dev.get('tenant_id')}", user=actor, request=request)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
class AdminCompanyComputeBody(BaseModel):
|
||||
discount: int | None = Field(default=None, ge=0, le=100)
|
||||
quota_add: int = Field(default=0, ge=0)
|
||||
|
||||
|
||||
@router.get("/park/tenants/{tid}/companies", summary="园区企业列表(含成员数/算力,平台)")
|
||||
async def admin_park_tenant_companies(
|
||||
tid: str,
|
||||
db: Database = Depends(get_db),
|
||||
_role: dict = Depends(require_roles("operator")),
|
||||
_perm: dict = Depends(require_permission("menu:admin_user_mgmt")),
|
||||
):
|
||||
from app.park import tenants as tnt
|
||||
return {"items": await tnt.list_companies(tid)}
|
||||
|
||||
|
||||
@router.put("/park/tenants/{tid}/companies/{cid}/compute", summary="设置企业算力(只增不减,平台兜底)")
|
||||
async def admin_park_tenant_company_compute(
|
||||
tid: str,
|
||||
cid: str,
|
||||
body: AdminCompanyComputeBody,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_permission("action:user.manage")),
|
||||
):
|
||||
from app.park import tenants as tnt
|
||||
c = await tnt.get_company(cid)
|
||||
if c is None or c.get("tenant_id") != tid:
|
||||
raise HTTPException(status_code=404, detail="企业不存在")
|
||||
try:
|
||||
updated = await tnt.set_company_compute(cid, discount=body.discount, quota_add=body.quota_add)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
await tnt.sync_company_engine(cid)
|
||||
if body.quota_add > 0:
|
||||
from ..services import compute_client
|
||||
for m in await tnt.company_members(cid):
|
||||
try:
|
||||
await compute_client.grant_user_quota_by_username(m["username"], body.quota_add)
|
||||
except Exception: # noqa: BLE001
|
||||
continue
|
||||
await write_audit(db, action="park.company_compute", resource="park_company",
|
||||
resource_id=cid, detail=f"discount={body.discount} quota_add={body.quota_add}", user=actor, request=request)
|
||||
return {"ok": True, "company": updated, "quota_add": body.quota_add}
|
||||
|
||||
Reference in New Issue
Block a user