feat(tasks): 阶段A剩余 —— 园区端/大屏任务 + 运营平台资金托管结算

- 大屏 /api/tasks 改为仅展示「当前园区可接」:本园专属(park_id)∪公有(park_public),状态published/claimed/doing
- 园区端 /api/park/tasks(本园发布+指派本园列表) /tasks/{id}/release(园区内发单→park_released+可见性) /tasks/{id}/assign(园区内指派→锁定被指派人)
- 运营托管结算(operator):/admin/escrows(台账) /admin/tasks/{id}/escrow(托管建档/充值,佣金5%) /admin/tasks/{id}/release(验收结算,划佣金+余额代付OPC),全部挂运营平台
This commit is contained in:
Pine
2026-08-27 19:07:20 +08:00
parent 076f96af74
commit ad1bcf5bbf
2 changed files with 153 additions and 5 deletions
+80 -1
View File
@@ -11,7 +11,7 @@ from fastapi import APIRouter, Depends, File, HTTPException, Request, Response,
from pydantic import BaseModel
from ..dependencies import get_db
from ..schemas.operator import TaskCreateRequest, TaskUpdateRequest, TaskStatusRequest, TaskAssignRequest, TaskRecommendRequest, TaskSelectRecommendRequest, ProviderCreateRequest, ProviderUpdateRequest, ContentCreateRequest, ContentStatusRequest, ContentUpdateRequest, ConfigUpdateRequest, ComputePingResponse, ComputeBalanceRequest, ComputeProvisionRequest, ComputeProvisionResponse, CourseCreateRequest, CourseStatusRequest, CourseUpdateRequest, ActivityCreateRequest, ActivityStatusRequest, ActivityUpdateRequest, BookingUpdateRequest, TestCreateRequest, TestStatusRequest
from ..schemas.operator import TaskCreateRequest, TaskUpdateRequest, TaskStatusRequest, TaskAssignRequest, TaskRecommendRequest, TaskSelectRecommendRequest, ProviderCreateRequest, ProviderUpdateRequest, ContentCreateRequest, ContentStatusRequest, ContentUpdateRequest, ContentReviewRequest, ConfigUpdateRequest, ComputePingResponse, ComputeBalanceRequest, ComputeProvisionRequest, ComputeProvisionResponse, CourseCreateRequest, CourseStatusRequest, CourseUpdateRequest, ActivityCreateRequest, ActivityStatusRequest, ActivityUpdateRequest, BookingUpdateRequest, TestCreateRequest, TestStatusRequest
from ...rbac import require_permission, require_roles, write_audit
from ...infrastructure.repositories import Database
from ...services import compute_client
@@ -20,6 +20,61 @@ from ...services import training_admin_bridge as training
router = APIRouter(prefix="/admin", tags=["admin-op"])
class EscrowDepositRequest(BaseModel):
amount: int = 0
@router.get("/escrows", summary="运营结算台账(资金托管)")
async def operator_escrows(
status: str | None = None,
db: Database = Depends(get_db),
_u: dict = Depends(require_roles("operator")),
):
"""运营平台资金托管台账:全部任务托管、佣金、状态(deposited/frozen/released/refunded)。"""
return {"items": await db.escrows.list(status=status)}
@router.post("/tasks/{task_id}/escrow", summary="任务托管建档/充值(运营平台托管)")
async def operator_escrow_deposit(
task_id: str,
req: EscrowDepositRequest,
request: Request,
db: Database = Depends(get_db),
_u: dict = Depends(require_roles("operator")),
):
"""发标方向运营平台托管账户建档/充值;已托管返回现有。金额默认取任务预算。"""
from ...services.settlement_service import COMMISSION_RATE
task = await db.tasks.get(task_id)
if task is None:
raise HTTPException(status_code=404, detail="任务不存在")
for e in await db.escrows.list():
if e["task_id"] == task_id:
return e
amount = req.amount or max(task.get("budget_min", 0), task.get("budget_max", 0))
commission = int(amount * COMMISSION_RATE)
esc = await db.escrows.create(task_id, task["title"], amount, commission)
await write_audit(db, action="escrow.deposit", resource="escrow", resource_id=esc["id"],
detail=f"amount={amount}", user=_u, request=request)
return esc
@router.post("/tasks/{task_id}/release", summary="运营结算(验收通过,划佣金+代付 OPC)")
async def operator_escrow_release(
task_id: str,
request: Request,
db: Database = Depends(get_db),
_u: dict = Depends(require_roles("operator")),
):
"""验收通过:任务须 completed;托管结算 released(平台佣金留存,余额代付 OPC)。"""
from ...services.settlement_service import SettlementService
released = await SettlementService(db).release_escrow(task_id)
await write_audit(db, action="escrow.release", resource="escrow", resource_id=released["id"],
detail=f"amount={released.get('amount')}", user=_u, request=request)
return released
@router.get("/tasks", summary="任务列表")
async def list_tasks(
status: str | None = None,
@@ -261,6 +316,30 @@ async def set_content_status(
return item
@router.post("/content/{content_id}/review", summary="资讯审核(通过/下架)")
async def review_content(
content_id: str,
req: ContentReviewRequest,
request: Request,
db: Database = Depends(get_db),
actor: dict = Depends(require_permission("action:content.manage")),
):
"""approve → 已发布 + 公开(C 端可见);offline → 已下线。"""
if req.decision == "approve":
item = await db.content.approve(content_id)
action = "content.approve"
elif req.decision == "offline":
item = await db.content.set_status(content_id, "offline")
action = "content.offline"
else:
raise HTTPException(status_code=400, detail="无效审核操作")
if item is None:
raise HTTPException(status_code=404, detail="Content not found")
await write_audit(db, action=action, resource="content", resource_id=content_id,
detail=item.get("title", ""), user=actor, request=request)
return item
@router.put("/content/{content_id}", summary="编辑内容")
async def update_content(
content_id: str,