积分消耗明细改版V1
This commit is contained in:
@@ -8,7 +8,12 @@ from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.dependencies import get_admin_user, get_db
|
||||
from app.enums.credit_balance import CreditBalanceSourceType
|
||||
from app.enums.credit_balance import (
|
||||
CREDIT_BALANCE_SOURCE_TYPE_LABELS,
|
||||
CREDIT_BALANCE_STATUS_LABELS,
|
||||
CREDIT_LEVEL_LABELS,
|
||||
CreditBalanceSourceType,
|
||||
)
|
||||
from app.models.credit.balance import UserCreditBalance
|
||||
from app.models.credit.product import CreditProduct
|
||||
from app.models.user import User
|
||||
@@ -248,7 +253,9 @@ async def list_user_credit_balances(
|
||||
{
|
||||
"id": item.id,
|
||||
"credit_level": item.credit_level,
|
||||
"credit_level_label": CREDIT_LEVEL_LABELS.get(item.credit_level, item.credit_level),
|
||||
"source_type": item.source_type,
|
||||
"source_type_label": CREDIT_BALANCE_SOURCE_TYPE_LABELS.get(item.source_type, item.source_type),
|
||||
"source_id": item.source_id,
|
||||
"grant_amount": float(item.grant_amount),
|
||||
"unspent_amount": float(item.unspent_amount),
|
||||
@@ -258,7 +265,8 @@ async def list_user_credit_balances(
|
||||
"valid_from": item.valid_from,
|
||||
"expires_at": item.expires_at,
|
||||
"last_usable_at": last_usable_at(item.expires_at),
|
||||
"status": effective_balance_status(item, request_time=checked_at),
|
||||
"status": (status_value := effective_balance_status(item, request_time=checked_at)),
|
||||
"status_label": CREDIT_BALANCE_STATUS_LABELS.get(status_value, status_value),
|
||||
}
|
||||
for item in result.scalars().all()
|
||||
]
|
||||
|
||||
@@ -16,13 +16,29 @@ from app.utils.id_gen import generate_id
|
||||
router = APIRouter(prefix="/admin/llm-billing", tags=["admin-llm-billing"])
|
||||
|
||||
|
||||
def _policy_out(policy: LlmBillingPolicyModel) -> dict:
|
||||
# 数据库 pre_deduct_credits 是历史物理字段名;Admin API 统一输出 charge_credits。
|
||||
return {
|
||||
"id": policy.id,
|
||||
"scene_code": policy.scene_code,
|
||||
"scene_name": policy.scene_name,
|
||||
"charge_credits": float(policy.pre_deduct_credits),
|
||||
"is_active": policy.is_active,
|
||||
"version": policy.version,
|
||||
"created_by": policy.created_by,
|
||||
"updated_by": policy.updated_by,
|
||||
"created_at": policy.created_at,
|
||||
"updated_at": policy.updated_at,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/policies")
|
||||
async def list_policies(
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(LlmBillingPolicyModel).order_by(LlmBillingPolicyModel.scene_code))
|
||||
return list(result.scalars().all())
|
||||
return [_policy_out(item) for item in result.scalars().all()]
|
||||
|
||||
|
||||
@router.post("/policies")
|
||||
@@ -33,14 +49,20 @@ async def create_policy(
|
||||
):
|
||||
if data.scene_code not in LLM_BILLING_SCENE_LABELS:
|
||||
raise HTTPException(status_code=400, detail="不支持的LLM业务场景")
|
||||
existing = await db.execute(select(LlmBillingPolicyModel.id).where(LlmBillingPolicyModel.scene_code == data.scene_code).limit(1))
|
||||
existing = await db.execute(
|
||||
select(LlmBillingPolicyModel.id)
|
||||
.where(LlmBillingPolicyModel.scene_code == data.scene_code)
|
||||
.limit(1)
|
||||
)
|
||||
if existing.scalar_one_or_none():
|
||||
raise HTTPException(status_code=409, detail="该LLM业务场景已存在")
|
||||
|
||||
policy = LlmBillingPolicyModel(
|
||||
id=generate_id(),
|
||||
scene_code=data.scene_code,
|
||||
scene_name=LLM_BILLING_SCENE_LABELS[data.scene_code],
|
||||
pre_deduct_credits=data.pre_deduct_credits,
|
||||
# 保留历史物理字段,不做无业务价值的数据库重命名迁移。
|
||||
pre_deduct_credits=data.charge_credits,
|
||||
is_active=data.is_active,
|
||||
version=1,
|
||||
created_by=admin.id,
|
||||
@@ -48,8 +70,17 @@ async def create_policy(
|
||||
)
|
||||
db.add(policy)
|
||||
await db.flush()
|
||||
log_operation_event(domain="llm_billing", module="admin", event_type="LLM_BILLING_POLICY_CREATED", user_id=admin.id, detail={"scene_code": policy.scene_code, "credits": float(policy.pre_deduct_credits)})
|
||||
return policy
|
||||
policy_id = policy.id
|
||||
scene_code = policy.scene_code
|
||||
charge_credits = float(policy.pre_deduct_credits)
|
||||
log_operation_event(
|
||||
domain="llm_billing",
|
||||
module="admin",
|
||||
event_type="LLM_BILLING_POLICY_CREATED",
|
||||
user_id=admin.id,
|
||||
detail={"scene_code": scene_code, "charge_credits": charge_credits, "policy_id": policy_id},
|
||||
)
|
||||
return _policy_out(policy)
|
||||
|
||||
|
||||
@router.put("/policies/{policy_id}")
|
||||
@@ -59,20 +90,41 @@ async def update_policy(
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(LlmBillingPolicyModel).where(LlmBillingPolicyModel.id == policy_id).limit(1).with_for_update())
|
||||
result = await db.execute(
|
||||
select(LlmBillingPolicyModel)
|
||||
.where(LlmBillingPolicyModel.id == policy_id)
|
||||
.limit(1)
|
||||
.with_for_update()
|
||||
)
|
||||
policy = result.scalar_one_or_none()
|
||||
if not policy:
|
||||
raise HTTPException(status_code=404, detail="LLM计费场景不存在")
|
||||
raise HTTPException(status_code=404, detail="LLM积分场景不存在")
|
||||
|
||||
payload = data.model_dump(exclude_unset=True)
|
||||
payload.pop("scene_name", None)
|
||||
for key, value in payload.items():
|
||||
setattr(policy, key, value)
|
||||
if "charge_credits" in payload:
|
||||
policy.pre_deduct_credits = payload.pop("charge_credits")
|
||||
if "is_active" in payload:
|
||||
policy.is_active = payload["is_active"]
|
||||
policy.scene_name = LLM_BILLING_SCENE_LABELS.get(policy.scene_code, policy.scene_name)
|
||||
policy.version += 1
|
||||
policy.updated_by = admin.id
|
||||
await db.flush()
|
||||
log_operation_event(domain="llm_billing", module="admin", event_type="LLM_BILLING_POLICY_UPDATED", user_id=admin.id, detail={"scene_code": policy.scene_code, "version": policy.version})
|
||||
return policy
|
||||
|
||||
log_operation_event(
|
||||
domain="llm_billing",
|
||||
module="admin",
|
||||
event_type="LLM_BILLING_POLICY_UPDATED",
|
||||
user_id=admin.id,
|
||||
detail={
|
||||
"scene_code": policy.scene_code,
|
||||
"policy_id": policy.id,
|
||||
"version": policy.version,
|
||||
"charge_credits": float(policy.pre_deduct_credits),
|
||||
"is_active": policy.is_active,
|
||||
},
|
||||
)
|
||||
return _policy_out(policy)
|
||||
|
||||
|
||||
@router.get("/executions")
|
||||
@@ -85,5 +137,12 @@ async def list_executions(
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
items, total = await list_executions_with_calls(db, page=page, page_size=page_size, scene_code=scene_code, status=status, user_id=user_id)
|
||||
items, total = await list_executions_with_calls(
|
||||
db,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
scene_code=scene_code,
|
||||
status=status,
|
||||
user_id=user_id,
|
||||
)
|
||||
return {"items": items, "total": total}
|
||||
|
||||
Reference in New Issue
Block a user