feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
142
server/src/app/api/v1/endpoints/commercial_billing.py
Normal file
142
server/src/app/api/v1/endpoints/commercial_billing.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""商业不可变账期和管理审计历史。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import (
|
||||
CurrentUserContext,
|
||||
get_current_user,
|
||||
get_db,
|
||||
require_platform_admin_user,
|
||||
)
|
||||
from app.schemas.commercial_billing import (
|
||||
CommercialAdminEventRead,
|
||||
CommercialBillingPeriodRead,
|
||||
CommercialBillingPeriodTenantRead,
|
||||
)
|
||||
from app.services.commercial_access_policy import (
|
||||
CommercialAccessPolicy,
|
||||
CommercialPermissionError,
|
||||
)
|
||||
from app.services.commercial_billing_periods import CommercialBillingPeriodService
|
||||
from app.services.commercial_queries import CommercialQueryService
|
||||
|
||||
router = APIRouter(prefix="/commercial")
|
||||
DbSession = Annotated[Session, Depends(get_db)]
|
||||
CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)]
|
||||
PlatformAdmin = Annotated[CurrentUserContext, Depends(require_platform_admin_user)]
|
||||
|
||||
|
||||
@router.get(
|
||||
"/billing-periods",
|
||||
response_model=list[CommercialBillingPeriodTenantRead],
|
||||
summary="读取当前租户不可变账期历史",
|
||||
)
|
||||
def list_current_tenant_billing_periods(
|
||||
db: DbSession,
|
||||
current_user: CurrentUser,
|
||||
subscription_id: str | None = None,
|
||||
as_of: datetime | None = None,
|
||||
limit: int = Query(default=200, ge=1, le=500),
|
||||
offset: int = Query(default=0, ge=0),
|
||||
) -> list[CommercialBillingPeriodTenantRead]:
|
||||
try:
|
||||
tenant_id = CommercialAccessPolicy.require_account_read(current_user)
|
||||
rows = _period_reads(
|
||||
db,
|
||||
tenant_id,
|
||||
subscription_id=subscription_id,
|
||||
as_of=as_of,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
)
|
||||
return [CommercialBillingPeriodTenantRead.model_validate(row.model_dump()) for row in rows]
|
||||
except CommercialPermissionError as error:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(error)) from error
|
||||
|
||||
|
||||
@router.get(
|
||||
"/admin/tenants/{tenant_id}/billing-periods",
|
||||
response_model=list[CommercialBillingPeriodRead],
|
||||
summary="平台管理员读取租户不可变账期历史",
|
||||
)
|
||||
def list_tenant_billing_periods(
|
||||
tenant_id: str,
|
||||
db: DbSession,
|
||||
current_user: PlatformAdmin,
|
||||
subscription_id: str | None = None,
|
||||
as_of: datetime | None = None,
|
||||
limit: int = Query(default=200, ge=1, le=500),
|
||||
offset: int = Query(default=0, ge=0),
|
||||
) -> list[CommercialBillingPeriodRead]:
|
||||
target = CommercialAccessPolicy.require_platform_admin(
|
||||
current_user,
|
||||
target_tenant_id=tenant_id,
|
||||
)
|
||||
return _period_reads(
|
||||
db,
|
||||
target,
|
||||
subscription_id=subscription_id,
|
||||
as_of=as_of,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/admin/tenants/{tenant_id}/admin-events",
|
||||
response_model=list[CommercialAdminEventRead],
|
||||
summary="平台管理员读取商业配置与续期审计",
|
||||
)
|
||||
def list_tenant_commercial_admin_events(
|
||||
tenant_id: str,
|
||||
db: DbSession,
|
||||
current_user: PlatformAdmin,
|
||||
action: str | None = None,
|
||||
resource_type: str | None = None,
|
||||
resource_id: str | None = None,
|
||||
start: datetime | None = None,
|
||||
end: datetime | None = None,
|
||||
limit: int = Query(default=200, ge=1, le=500),
|
||||
offset: int = Query(default=0, ge=0),
|
||||
) -> list[CommercialAdminEventRead]:
|
||||
target = CommercialAccessPolicy.require_platform_admin(
|
||||
current_user,
|
||||
target_tenant_id=tenant_id,
|
||||
)
|
||||
try:
|
||||
return CommercialQueryService(db).list_admin_events(
|
||||
target,
|
||||
action=action,
|
||||
resource_type=resource_type,
|
||||
resource_id=resource_id,
|
||||
start=start,
|
||||
end=end,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
)
|
||||
except ValueError as error:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error
|
||||
|
||||
|
||||
def _period_reads(
|
||||
db: Session,
|
||||
tenant_id: str,
|
||||
*,
|
||||
subscription_id: str | None,
|
||||
as_of: datetime | None,
|
||||
limit: int,
|
||||
offset: int,
|
||||
) -> list[CommercialBillingPeriodRead]:
|
||||
rows = CommercialQueryService(db).list_billing_periods(
|
||||
tenant_id,
|
||||
subscription_id=subscription_id,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
)
|
||||
return [CommercialBillingPeriodService.to_read(row, as_of=as_of) for row in rows]
|
||||
Reference in New Issue
Block a user