Files
server-core/app/api/routers/rbac_portals.py
T
Pine d851b54755 refactor: 角色组织模型重构 - 阶段二完成
1. 运行迁移脚本:同步 2 条园区管理员 + 1 条企业管理员到 organization_members
2. 新增 get_user_organizations / get_user_primary_org 辅助函数
3. 改造各路由优先从 organization_members 查询组织:
   - rbac_org.py: /org/me 返回所有绑定组织
   - rbac_portals.py: referrals 和 carrier_park 优先用 organization_members
   - rbac_hall.py: 发布岗位优先用 organization_members 获取企业
4. users.org_id 保留作回退,不删除字段,风险可控
2026-09-03 09:18:28 +08:00

173 lines
7.3 KiB
Python

# -*- coding: utf-8 -*-
"""各业务端口工作台与子页面端点(仅保留园区 carrier 端)。
列表类页面数据全部由服务端(portal_pages 表)提供。
OPC 端工作台在 ``rbac_opc.py`` 中(按用户聚合)。
运营端工作台在 ``rbac_operator.py`` 中。
已移除:enterprise / provider / government 端路由。
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
from ..dependencies import get_db, get_user_primary_org
from ..schemas.portals import ReferralCreate
from ...rbac import require_roles, write_audit
from ...infrastructure.repositories import Database
router = APIRouter(tags=["portals"])
def _dashboard(port: str):
role = "carrier"
async def handler(
db: Database = Depends(get_db),
_u: dict = Depends(require_roles(role)),
):
payload = await db.portal_dashboards.get(port)
if payload is None:
raise HTTPException(status_code=404, detail="Dashboard not found")
return payload
return handler
def _page(port: str, page: str):
role = "carrier"
async def handler(
db: Database = Depends(get_db),
_u: dict = Depends(require_roles(role)),
):
payload = await db.portal_pages.get(port, page)
if payload is None:
raise HTTPException(status_code=404, detail="Page data not found")
payload.setdefault("port", port)
payload.setdefault("page", page)
return payload
return handler
router.get("/carrier/dashboard", summary="载体/园区端工作台")(_dashboard("carrier"))
# ── 子页面(列表/数据均由服务端提供)──────────────────────────────────────
router.get("/carrier/opc", summary="载体·OPC 管理")(_page("carrier", "opc"))
router.get("/carrier/services", summary="载体·服务管理")(_page("carrier", "services"))
# ── 载体·服务商引荐(三方绑定:载体引荐服务商→服务园区内 OPC)─────────────
@router.get("/carrier/referrals", summary="载体·引荐记录")
async def carrier_referrals(
db: Database = Depends(get_db),
user: dict = Depends(require_roles("carrier")),
):
carrier_org = await get_user_primary_org(db, user, org_type="carrier")
carrier_id = carrier_org["org_id"] if carrier_org else user.get("org_id")
return {"items": await db.referrals.list_for(carrier_id)}
@router.post("/carrier/referrals", summary="载体·引荐服务商给园区内 OPC")
async def carrier_create_referral(
req: ReferralCreate,
request: Request,
db: Database = Depends(get_db),
actor: dict = Depends(require_roles("carrier")),
):
carrier_org = await get_user_primary_org(db, actor, org_type="carrier")
carrier_id = carrier_org["org_id"] if carrier_org else actor.get("org_id") or actor["id"]
referral = await db.referrals.create({
**req.model_dump(exclude_none=True), "carrier_id": carrier_id,
})
await write_audit(db, action="referral.create", resource="referral", resource_id=referral["id"],
detail=f"{referral['provider_name']}->{referral['opc_name']}", user=actor, request=request)
return referral
# ── 载体·任务管理(平台级任务展示 + 本载体下架/上架;不可编辑任务内容)────────
def _carrier_park_id(user: dict) -> str:
"""载体用户 → 其管理的园区 id(取第一个有效园区成员关系)。"""
return user.get("park_id") or user.get("tenant_id") or ""
async def _resolve_carrier_park(db: Database, user: dict) -> str:
"""解析载体用户所属园区:优先 organization_members 中的 carrier 组织,回退旧表。"""
carrier_org = await get_user_primary_org(db, user, org_type="carrier")
if carrier_org:
return carrier_org["org_id"]
park_id = _carrier_park_id(user)
if park_id:
return park_id
members = await db.park_members.list_by_user(user["id"])
return members["park_id"] if members else ""
def _is_platform_task(t: dict) -> bool:
"""平台级任务:非本园区专属、公开可下发的任务(园区专属 park_id 任务不算)。"""
if t.get("park_id"):
return False
return t.get("visibility", "public") in ("public", "c_visible") or t.get("park_public")
@router.get("/carrier/tasks", summary="载体端任务管理(平台级任务 + 本载体下架状态)")
async def carrier_tasks(
db: Database = Depends(get_db),
user: dict = Depends(require_roles("carrier")),
):
park_id = await _resolve_carrier_park(db, user)
tasks = [t for t in await db.tasks.list_published()
if t.get("status") in ("published", "claimed", "doing") and _is_platform_task(t)]
hidden = await db.task_park_visibility.hidden_ids([t["id"] for t in tasks], park_id)
items = []
for t in tasks:
items.append({
"id": t["id"], "task_code": t.get("task_code", ""), "title": t.get("title", ""),
"category": t.get("category", ""), "sub_category": t.get("sub_category", ""),
"mode": t.get("mode", ""), "status": t.get("status", ""),
"budget_min": t.get("budget_min", 0), "budget_max": t.get("budget_max", 0),
"deadline": t.get("deadline", ""), "delivery_days": t.get("delivery_days", 0),
"headcount": t.get("headcount", 0), "publisher_name": t.get("publisher_name", ""),
"publisher_role": t.get("publisher_role", ""),
"display_priority": t.get("display_priority", 0),
"hidden": t["id"] in hidden,
})
items.sort(key=lambda x: -x.get("display_priority", 0))
return {"items": items, "park_id": park_id}
async def _carrier_task_visibility(
task_id: str, visible: bool, request: Request,
db: Database, actor: dict,
) -> dict:
park_id = await _resolve_carrier_park(db, actor)
if not park_id:
raise HTTPException(status_code=400, detail="载体未绑定园区,无法管理任务展示")
task = await db.tasks.get(task_id)
if task is None:
raise HTTPException(status_code=404, detail="任务不存在")
if not _is_platform_task(task):
raise HTTPException(status_code=403, detail="仅平台级任务可在载体端管理展示")
rec = await db.task_park_visibility.set(task_id, park_id, visible, actor["id"])
await write_audit(db, action="carrier.task.visibility", resource="task", resource_id=task_id,
detail=f"visible={visible} park={park_id}", user=actor, request=request)
return {"ok": True, "task_id": task_id, "park_id": park_id, "hidden": not visible}
@router.post("/carrier/tasks/{task_id}/hide", summary="载体端任务下架(本载体隐藏,不影响其他端)")
async def carrier_task_hide(
task_id: str,
request: Request,
db: Database = Depends(get_db),
actor: dict = Depends(require_roles("carrier")),
):
return await _carrier_task_visibility(task_id, False, request, db, actor)
@router.post("/carrier/tasks/{task_id}/unhide", summary="载体端任务上架(恢复本载体展示)")
async def carrier_task_unhide(
task_id: str,
request: Request,
db: Database = Depends(get_db),
actor: dict = Depends(require_roles("carrier")),
):
return await _carrier_task_visibility(task_id, True, request, db, actor)