4dedb43d7f
数据模型: - 新增 tenant_discounts(运营端载体折扣) - 新增 tenant_user_discounts(载体端账号折扣) - 新增 compute_recharges(算力充值记录) - 新增 compute_balance_allocations(余额分配记录) - 新增 compute_usage_records(算力使用记录) - 扩展 park_tenants(default_company_discount, default_user_discount) - 扩展 park_companies(compute_balance, compute_balance_used) - 扩展 company_members(role, compute_balance, compute_balance_used, left_at, left_reason) 核心服务: - compute_pricing_service.py:四级折扣价格计算、余额扣费、企业余额管理 API接口(/api/compute): - 运营端:载体折扣管理、标准价管理 - 载体端:企业折扣设置、账号折扣设置、默认折扣设置 - 企业端:余额充值、成员余额分配/回收、充值/分配记录 - 个人端:我的余额、使用记录、退出企业 - 内部:价格查询、算力扣费 数据库迁移:0064_compute_discount_balance.py
662 lines
25 KiB
Python
662 lines
25 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""算力多级折扣与余额管理 API。
|
||
|
||
包含:
|
||
- 运营端:载体折扣管理、标准价管理
|
||
- 载体端:企业折扣设置、账号折扣设置
|
||
- 企业端:余额充值、成员余额分配
|
||
- 个人端:我的余额、退出企业
|
||
- 内部:价格查询、算力扣费
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import uuid
|
||
from datetime import datetime
|
||
from typing import Optional
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||
from pydantic import BaseModel
|
||
from sqlalchemy import select, and_
|
||
|
||
from ..dependencies import get_db, get_current_user
|
||
from ..rbac import write_audit, require_role
|
||
from ...infrastructure.models import (
|
||
ParkTenant, ParkCompany, CompanyMember,
|
||
TenantDiscount, TenantUserDiscount,
|
||
ComputeRecharge, ComputeBalanceAllocation, ComputeUsageRecord,
|
||
)
|
||
from ...infrastructure.repositories import Database
|
||
from ...services.compute_pricing_service import (
|
||
calculate_compute_price, deduct_compute_balance,
|
||
get_company_balance, allocate_balance_to_member, reclaim_balance_from_member,
|
||
STANDARD_PRICES,
|
||
)
|
||
|
||
router = APIRouter(prefix="/api/compute", tags=["compute-pricing"])
|
||
|
||
|
||
def now_str() -> str:
|
||
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
|
||
|
||
def new_id() -> str:
|
||
return uuid.uuid4().hex[:24]
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
# 运营端:载体折扣管理
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
|
||
class TenantDiscountBody(BaseModel):
|
||
tenant_id: str
|
||
discount: int = 0
|
||
effective_date: str = ""
|
||
expire_date: str = ""
|
||
reason: str = ""
|
||
|
||
|
||
@router.get("/admin/tenant-discounts", summary="运营端:载体折扣列表")
|
||
async def admin_tenant_discounts(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
require_role(user, ["operator"])
|
||
result = await db.session.execute(select(TenantDiscount).order_by(TenantDiscount.created_at.desc()))
|
||
items = []
|
||
for td in result.scalars().all():
|
||
tenant = await db.session.get(ParkTenant, td.tenant_id)
|
||
items.append({
|
||
"id": td.id,
|
||
"tenant_id": td.tenant_id,
|
||
"tenant_name": tenant.name if tenant else "",
|
||
"discount": td.discount,
|
||
"effective_date": td.effective_date,
|
||
"expire_date": td.expire_date,
|
||
"reason": td.reason,
|
||
"created_by": td.created_by,
|
||
"created_at": td.created_at,
|
||
})
|
||
return {"items": items}
|
||
|
||
|
||
@router.post("/admin/tenant-discounts", summary="运营端:设置载体折扣")
|
||
async def admin_set_tenant_discount(
|
||
body: TenantDiscountBody,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
require_role(user, ["operator"])
|
||
body.discount = max(0, min(100, int(body.discount)))
|
||
|
||
# 检查载体是否存在
|
||
tenant = await db.session.get(ParkTenant, body.tenant_id)
|
||
if not tenant:
|
||
raise HTTPException(status_code=404, detail="载体不存在")
|
||
|
||
# 检查是否已有折扣,有则更新
|
||
result = await db.session.execute(
|
||
select(TenantDiscount).where(TenantDiscount.tenant_id == body.tenant_id)
|
||
)
|
||
td = result.scalars().first()
|
||
if td:
|
||
td.discount = body.discount
|
||
td.effective_date = body.effective_date
|
||
td.expire_date = body.expire_date
|
||
td.reason = body.reason
|
||
td.created_by = user.get("id", "")
|
||
td.created_at = now_str()
|
||
else:
|
||
td = TenantDiscount(
|
||
id=new_id(),
|
||
tenant_id=body.tenant_id,
|
||
discount=body.discount,
|
||
effective_date=body.effective_date,
|
||
expire_date=body.expire_date,
|
||
reason=body.reason,
|
||
created_by=user.get("id", ""),
|
||
created_at=now_str(),
|
||
)
|
||
db.session.add(td)
|
||
|
||
await db.session.commit()
|
||
await write_audit(db, action="compute.tenant_discount_set", resource="tenant_discount",
|
||
resource_id=td.id, detail=f"tenant={body.tenant_id}, discount={body.discount}",
|
||
user=user, request=request)
|
||
return {"ok": True, "id": td.id}
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
# 运营端:标准价管理
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
|
||
class StandardPriceBody(BaseModel):
|
||
model: str
|
||
price_per_1k: int # 每1000token价格(分)
|
||
|
||
|
||
@router.get("/admin/standard-prices", summary="运营端:标准价列表")
|
||
async def admin_standard_prices(
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
require_role(user, ["operator"])
|
||
items = [{"model": k, "price_per_1k": v} for k, v in STANDARD_PRICES.items()]
|
||
return {"items": items}
|
||
|
||
|
||
@router.put("/admin/standard-prices/{model}", summary="运营端:修改标准价")
|
||
async def admin_update_standard_price(
|
||
model: str,
|
||
body: StandardPriceBody,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
require_role(user, ["operator"])
|
||
STANDARD_PRICES[model] = max(1, int(body.price_per_1k))
|
||
await write_audit(db, action="compute.standard_price_update", resource="standard_price",
|
||
resource_id=model, detail=f"price={body.price_per_1k}",
|
||
user=user, request=request)
|
||
return {"ok": True, "model": model, "price_per_1k": STANDARD_PRICES[model]}
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
# 载体端:折扣设置
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
|
||
class TenantDiscountSettingsBody(BaseModel):
|
||
default_company_discount: int = 0
|
||
default_user_discount: int = 0
|
||
|
||
|
||
class CompanyDiscountBody(BaseModel):
|
||
discount: int = 0
|
||
|
||
|
||
class UserDiscountBody(BaseModel):
|
||
user_id: str
|
||
discount: int = 0
|
||
reason: str = ""
|
||
|
||
|
||
async def _get_current_tenant(db: Database, user: dict) -> Optional[ParkTenant]:
|
||
"""获取当前用户管理的载体。"""
|
||
# 通过operator_user_id查找
|
||
result = await db.session.execute(
|
||
select(ParkTenant).where(ParkTenant.operator_user_id == user.get("id", ""))
|
||
)
|
||
return result.scalars().first()
|
||
|
||
|
||
@router.get("/tenant/discount-settings", summary="载体端:获取折扣设置")
|
||
async def tenant_discount_settings(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
tenant = await _get_current_tenant(db, user)
|
||
if not tenant:
|
||
raise HTTPException(status_code=403, detail="未绑定载体")
|
||
return {
|
||
"tenant_id": tenant.id,
|
||
"tenant_name": tenant.name,
|
||
"default_company_discount": tenant.default_company_discount or 0,
|
||
"default_user_discount": tenant.default_user_discount or 0,
|
||
}
|
||
|
||
|
||
@router.put("/tenant/discount-settings", summary="载体端:更新默认折扣")
|
||
async def tenant_update_discount_settings(
|
||
body: TenantDiscountSettingsBody,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
tenant = await _get_current_tenant(db, user)
|
||
if not tenant:
|
||
raise HTTPException(status_code=403, detail="未绑定载体")
|
||
|
||
body.default_company_discount = max(0, min(100, int(body.default_company_discount)))
|
||
body.default_user_discount = max(0, min(100, int(body.default_user_discount)))
|
||
|
||
tenant.default_company_discount = body.default_company_discount
|
||
tenant.default_user_discount = body.default_user_discount
|
||
await db.session.commit()
|
||
|
||
await write_audit(db, action="compute.tenant_discount_settings_update", resource="park_tenant",
|
||
resource_id=tenant.id, user=user, request=request)
|
||
return {"ok": True}
|
||
|
||
|
||
@router.get("/tenant/company-discounts", summary="载体端:下属企业折扣列表")
|
||
async def tenant_company_discounts(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
tenant = await _get_current_tenant(db, user)
|
||
if not tenant:
|
||
raise HTTPException(status_code=403, detail="未绑定载体")
|
||
|
||
result = await db.session.execute(
|
||
select(ParkCompany).where(ParkCompany.tenant_id == tenant.id)
|
||
)
|
||
items = []
|
||
for c in result.scalars().all():
|
||
items.append({
|
||
"company_id": c.id,
|
||
"company_name": c.name,
|
||
"discount": c.compute_discount or 0,
|
||
"industry": c.industry,
|
||
"status": c.status,
|
||
})
|
||
return {"items": items}
|
||
|
||
|
||
@router.put("/tenant/company-discounts/{company_id}", summary="载体端:设置企业折扣")
|
||
async def tenant_set_company_discount(
|
||
company_id: str,
|
||
body: CompanyDiscountBody,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
tenant = await _get_current_tenant(db, user)
|
||
if not tenant:
|
||
raise HTTPException(status_code=403, detail="未绑定载体")
|
||
|
||
company = await db.session.get(ParkCompany, company_id)
|
||
if not company or company.tenant_id != tenant.id:
|
||
raise HTTPException(status_code=404, detail="企业不存在或不属于本载体")
|
||
|
||
body.discount = max(0, min(100, int(body.discount)))
|
||
company.compute_discount = body.discount
|
||
await db.session.commit()
|
||
|
||
await write_audit(db, action="compute.company_discount_set", resource="park_company",
|
||
resource_id=company_id, detail=f"discount={body.discount}",
|
||
user=user, request=request)
|
||
return {"ok": True}
|
||
|
||
|
||
@router.get("/tenant/user-discounts", summary="载体端:下属账号折扣列表")
|
||
async def tenant_user_discounts(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
tenant = await _get_current_tenant(db, user)
|
||
if not tenant:
|
||
raise HTTPException(status_code=403, detail="未绑定载体")
|
||
|
||
result = await db.session.execute(
|
||
select(TenantUserDiscount).where(TenantUserDiscount.tenant_id == tenant.id)
|
||
)
|
||
items = []
|
||
for tud in result.scalars().all():
|
||
items.append({
|
||
"id": tud.id,
|
||
"user_id": tud.user_id,
|
||
"discount": tud.discount,
|
||
"reason": tud.reason,
|
||
"created_at": tud.created_at,
|
||
})
|
||
return {"items": items}
|
||
|
||
|
||
@router.post("/tenant/user-discounts", summary="载体端:设置账号折扣")
|
||
async def tenant_set_user_discount(
|
||
body: UserDiscountBody,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
tenant = await _get_current_tenant(db, user)
|
||
if not tenant:
|
||
raise HTTPException(status_code=403, detail="未绑定载体")
|
||
|
||
body.discount = max(0, min(100, int(body.discount)))
|
||
|
||
# 检查是否已有折扣
|
||
result = await db.session.execute(
|
||
select(TenantUserDiscount).where(
|
||
and_(TenantUserDiscount.tenant_id == tenant.id, TenantUserDiscount.user_id == body.user_id)
|
||
)
|
||
)
|
||
tud = result.scalars().first()
|
||
if tud:
|
||
tud.discount = body.discount
|
||
tud.reason = body.reason
|
||
tud.created_by = user.get("id", "")
|
||
tud.created_at = now_str()
|
||
else:
|
||
tud = TenantUserDiscount(
|
||
id=new_id(),
|
||
tenant_id=tenant.id,
|
||
user_id=body.user_id,
|
||
discount=body.discount,
|
||
reason=body.reason,
|
||
created_by=user.get("id", ""),
|
||
created_at=now_str(),
|
||
)
|
||
db.session.add(tud)
|
||
|
||
await db.session.commit()
|
||
await write_audit(db, action="compute.user_discount_set", resource="tenant_user_discount",
|
||
resource_id=tud.id, detail=f"user={body.user_id}, discount={body.discount}",
|
||
user=user, request=request)
|
||
return {"ok": True, "id": tud.id}
|
||
|
||
|
||
@router.delete("/tenant/user-discounts/{discount_id}", summary="载体端:删除账号折扣")
|
||
async def tenant_delete_user_discount(
|
||
discount_id: str,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
tenant = await _get_current_tenant(db, user)
|
||
if not tenant:
|
||
raise HTTPException(status_code=403, detail="未绑定载体")
|
||
|
||
tud = await db.session.get(TenantUserDiscount, discount_id)
|
||
if not tud or tud.tenant_id != tenant.id:
|
||
raise HTTPException(status_code=404, detail="折扣记录不存在")
|
||
|
||
await db.session.delete(tud)
|
||
await db.session.commit()
|
||
await write_audit(db, action="compute.user_discount_delete", resource="tenant_user_discount",
|
||
resource_id=discount_id, user=user, request=request)
|
||
return {"ok": True}
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
# 企业端:余额管理
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
|
||
class RechargeBody(BaseModel):
|
||
amount: int # 充值金额(分)
|
||
payment_method: str = "wechat"
|
||
|
||
|
||
class AllocateBody(BaseModel):
|
||
user_id: str
|
||
amount: int
|
||
reason: str = ""
|
||
|
||
|
||
async def _get_admin_company(db: Database, company_id: str, user: dict) -> ParkCompany:
|
||
"""校验当前用户是该企业的管理员。"""
|
||
result = await db.session.execute(
|
||
select(CompanyMember).where(
|
||
and_(CompanyMember.company_id == company_id, CompanyMember.user_id == user.get("id", ""))
|
||
)
|
||
)
|
||
member = result.scalars().first()
|
||
if not member or not member.is_admin:
|
||
raise HTTPException(status_code=403, detail="仅企业管理员可操作")
|
||
|
||
company = await db.session.get(ParkCompany, company_id)
|
||
if not company:
|
||
raise HTTPException(status_code=404, detail="企业不存在")
|
||
return company
|
||
|
||
|
||
@router.get("/enterprise/{company_id}/balance", summary="企业端:余额总览")
|
||
async def enterprise_balance(
|
||
company_id: str,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
await _get_admin_company(db, company_id, user)
|
||
return await get_company_balance(db, company_id)
|
||
|
||
|
||
@router.post("/enterprise/{company_id}/recharge", summary="企业端:充值")
|
||
async def enterprise_recharge(
|
||
company_id: str,
|
||
body: RechargeBody,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
company = await _get_admin_company(db, company_id, user)
|
||
|
||
if body.amount <= 0:
|
||
raise HTTPException(status_code=400, detail="充值金额必须大于0")
|
||
|
||
# 创建充值记录(模拟支付成功)
|
||
recharge = ComputeRecharge(
|
||
id=new_id(),
|
||
company_id=company_id,
|
||
amount=body.amount,
|
||
compute_amount=body.amount, # 1:1到账
|
||
payment_method=body.payment_method,
|
||
payment_status="paid",
|
||
recharged_by=user.get("id", ""),
|
||
created_at=now_str(),
|
||
paid_at=now_str(),
|
||
)
|
||
db.session.add(recharge)
|
||
|
||
# 增加企业余额
|
||
company.compute_balance = (company.compute_balance or 0) + body.amount
|
||
await db.session.commit()
|
||
|
||
await write_audit(db, action="compute.enterprise_recharge", resource="compute_recharge",
|
||
resource_id=recharge.id, detail=f"amount={body.amount}",
|
||
user=user, request=request)
|
||
return {"ok": True, "recharge_id": recharge.id, "balance": company.compute_balance}
|
||
|
||
|
||
@router.get("/enterprise/{company_id}/recharges", summary="企业端:充值记录")
|
||
async def enterprise_recharges(
|
||
company_id: str,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
await _get_admin_company(db, company_id, user)
|
||
result = await db.session.execute(
|
||
select(ComputeRecharge).where(ComputeRecharge.company_id == company_id).order_by(ComputeRecharge.created_at.desc())
|
||
)
|
||
items = [{"id": r.id, "amount": r.amount, "compute_amount": r.compute_amount,
|
||
"payment_status": r.payment_status, "created_at": r.created_at}
|
||
for r in result.scalars().all()]
|
||
return {"items": items}
|
||
|
||
|
||
@router.post("/enterprise/{company_id}/allocate", summary="企业端:给成员分配余额")
|
||
async def enterprise_allocate(
|
||
company_id: str,
|
||
body: AllocateBody,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
await _get_admin_company(db, company_id, user)
|
||
result = await allocate_balance_to_member(db, company_id, user.get("id", ""), body.user_id, body.amount, body.reason)
|
||
if not result.get("success"):
|
||
raise HTTPException(status_code=400, detail=result.get("reason", "分配失败"))
|
||
await write_audit(db, action="compute.balance_allocate", resource="compute_balance_allocation",
|
||
resource_id=result["allocation_id"], detail=f"user={body.user_id}, amount={body.amount}",
|
||
user=user, request=request)
|
||
return result
|
||
|
||
|
||
@router.post("/enterprise/{company_id}/reclaim", summary="企业端:回收成员余额")
|
||
async def enterprise_reclaim(
|
||
company_id: str,
|
||
body: AllocateBody,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
await _get_admin_company(db, company_id, user)
|
||
result = await reclaim_balance_from_member(db, company_id, user.get("id", ""), body.user_id, body.amount, body.reason)
|
||
if not result.get("success"):
|
||
raise HTTPException(status_code=400, detail=result.get("reason", "回收失败"))
|
||
await write_audit(db, action="compute.balance_reclaim", resource="compute_balance_allocation",
|
||
resource_id=result["allocation_id"], detail=f"user={body.user_id}, amount={body.amount}",
|
||
user=user, request=request)
|
||
return result
|
||
|
||
|
||
@router.get("/enterprise/{company_id}/allocations", summary="企业端:分配记录")
|
||
async def enterprise_allocations(
|
||
company_id: str,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
await _get_admin_company(db, company_id, user)
|
||
result = await db.session.execute(
|
||
select(ComputeBalanceAllocation).where(ComputeBalanceAllocation.company_id == company_id).order_by(ComputeBalanceAllocation.created_at.desc())
|
||
)
|
||
items = [{"id": a.id, "from_user_id": a.from_user_id, "to_user_id": a.to_user_id,
|
||
"amount": a.amount, "type": a.type, "reason": a.reason, "created_at": a.created_at}
|
||
for a in result.scalars().all()]
|
||
return {"items": items}
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
# 个人端:我的余额、退出企业
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
|
||
@router.get("/user/balance", summary="个人端:我的余额")
|
||
async def user_balance(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
user_id = user.get("id", "")
|
||
|
||
# 获取企业分配的余额
|
||
result = await db.session.execute(
|
||
select(CompanyMember).where(
|
||
and_(CompanyMember.user_id == user_id, CompanyMember.status == "active")
|
||
)
|
||
)
|
||
company_balances = []
|
||
total_company_balance = 0
|
||
for m in result.scalars().all():
|
||
company = await db.session.get(ParkCompany, m.company_id)
|
||
if company:
|
||
balance = m.compute_balance or 0
|
||
total_company_balance += balance
|
||
company_balances.append({
|
||
"company_id": m.company_id,
|
||
"company_name": company.name,
|
||
"balance": balance,
|
||
"used": m.compute_balance_used or 0,
|
||
"role": m.role or "staff",
|
||
"joined_at": m.created_at,
|
||
})
|
||
|
||
# 个人余额(暂时为0)
|
||
personal_balance = 0
|
||
|
||
return {
|
||
"personal_balance": personal_balance,
|
||
"total_company_balance": total_company_balance,
|
||
"total_balance": personal_balance + total_company_balance,
|
||
"company_balances": company_balances,
|
||
}
|
||
|
||
|
||
@router.get("/user/usage", summary="个人端:使用记录")
|
||
async def user_usage(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
user_id = user.get("id", "")
|
||
result = await db.session.execute(
|
||
select(ComputeUsageRecord).where(ComputeUsageRecord.user_id == user_id).order_by(ComputeUsageRecord.created_at.desc()).limit(100)
|
||
)
|
||
items = [{"id": r.id, "model": r.model, "token_count": r.token_count,
|
||
"standard_price": r.standard_price, "discount": r.discount,
|
||
"discount_source": r.discount_source, "actual_amount": r.actual_amount,
|
||
"balance_source": r.balance_source, "company_id": r.company_id,
|
||
"created_at": r.created_at}
|
||
for r in result.scalars().all()]
|
||
return {"items": items}
|
||
|
||
|
||
class LeaveCompanyBody(BaseModel):
|
||
reason: str = ""
|
||
|
||
|
||
@router.post("/user/companies/{company_id}/leave", summary="个人端:退出企业")
|
||
async def user_leave_company(
|
||
company_id: str,
|
||
body: LeaveCompanyBody,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
user_id = user.get("id", "")
|
||
|
||
result = await db.session.execute(
|
||
select(CompanyMember).where(
|
||
and_(CompanyMember.company_id == company_id, CompanyMember.user_id == user_id)
|
||
)
|
||
)
|
||
member = result.scalars().first()
|
||
if not member:
|
||
raise HTTPException(status_code=404, detail="您不是该企业成员")
|
||
|
||
if member.is_admin:
|
||
raise HTTPException(status_code=400, detail="企业管理员不可主动退出,请先转让管理员权限")
|
||
|
||
# 回收剩余企业分配的余额
|
||
remaining_balance = member.compute_balance or 0
|
||
if remaining_balance > 0:
|
||
company = await db.session.get(ParkCompany, company_id)
|
||
if company:
|
||
company.compute_balance_used = max(0, (company.compute_balance_used or 0) - remaining_balance)
|
||
|
||
# 设置退出状态
|
||
member.status = "left"
|
||
member.left_at = now_str()
|
||
member.left_reason = body.reason
|
||
member.compute_balance = 0
|
||
|
||
await db.session.commit()
|
||
await write_audit(db, action="compute.user_leave_company", resource="company_member",
|
||
resource_id=member.id, detail=f"company={company_id}, refund={remaining_balance}",
|
||
user=user, request=request)
|
||
return {"ok": True, "refunded_balance": remaining_balance}
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
# 内部接口:价格查询、算力扣费
|
||
# ═══════════════════════════════════════════════════════════════════
|
||
|
||
class PriceQueryBody(BaseModel):
|
||
model: str
|
||
token_count: int
|
||
|
||
|
||
@router.post("/price", summary="内部:查询实际价格")
|
||
async def compute_price(
|
||
body: PriceQueryBody,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
return await calculate_compute_price(db, user.get("id", ""), body.model, body.token_count)
|
||
|
||
|
||
class DeductBody(BaseModel):
|
||
model: str
|
||
token_count: int
|
||
|
||
|
||
@router.post("/deduct", summary="内部:算力扣费")
|
||
async def compute_deduct(
|
||
body: DeductBody,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
result = await deduct_compute_balance(db, user.get("id", ""), body.model, body.token_count)
|
||
if not result.get("success"):
|
||
raise HTTPException(status_code=402, detail=result.get("reason", "扣费失败"))
|
||
await write_audit(db, action="compute.deduct", resource="compute_usage_record",
|
||
resource_id="", detail=f"model={body.model}, tokens={body.token_count}, amount={result['amount']}",
|
||
user=user, request=request)
|
||
return result
|