Files
server-core/app/api/routers/rbac_investor.py
T
Pine 01ef3c9637 refactor: schemas 抽取(DTO 从 models.py 与路由内联迁至 api/schemas/)
- 新建 api/schemas/:auth/agents/admin/ecosystem/investor/opc/operator/org/portals
- models.py 移除 15 个 Pydantic DTO(保留 ORM 数据模型层)
- 13 个路由内联 DTO 全部迁出,路由瘦身仅剩处理逻辑
- 全量 66 测试通过
2026-08-24 09:51:52 +08:00

190 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""投资人端端点:投资方向/项目/意向/路演(按权限与范围审核)/培训/组合。
路演发布审核规则:
- 运营方发布 → 免审(自身最高权限)。
- 政务在自身权限范围(scope ⊆ publisher.scope_region_ids)内发布 → 免审;超出 → 需运营方审核。
- 投资机构/载体/企业发布 → 一律需运营方审核。
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
from ..dependencies import get_db
from ..schemas.investor import PreferenceRequest, IntentRequest, RoadshowCreate
from ...rbac import require_roles, write_audit
from ...infrastructure.repositories import Database
router = APIRouter(prefix="/investor", tags=["investor"])
# 可发布投融资活动的端口角色(审核规则见 _need_review
_ROADSHOW_ROLES = ("investor", "government", "operator", "carrier", "enterprise")
def _need_review(user: dict, req: RoadshowCreate, db: Database) -> bool:
"""按发布主体与范围判断是否需要审核。"""
role = user.get("role")
if role == "operator":
return False
if role == "government":
scope = user.get("scope_region_ids", [])
if req.scope_type == "region" and req.region_id and req.region_id in scope:
return False # 自身权限范围内,免审
return True # 超出范围,需审核
# 投资机构/载体/企业:一律需审核
return True
def _pub_status(need_review: bool) -> str:
return "submitted" if need_review else "published"
@router.get("/dashboard", summary="投资人工作台")
async def investor_dashboard(
db: Database = Depends(get_db),
user: dict = Depends(require_roles("investor")),
):
pref = await db.investor_prefs.get(user["id"]) or {}
portfolio = await db.portal_pages.get("investor", "portfolio") or {}
roadshows = [r for r in await db.roadshows.list(status="published")][:3]
return {
"preference": pref,
"stats": portfolio.get("stats", []),
"intents": len(await db.intents.list_for(user["id"])),
"recentRoadshows": roadshows,
}
@router.get("/preferences", summary="投资方向")
async def get_preferences(
db: Database = Depends(get_db),
user: dict = Depends(require_roles("investor")),
):
return await db.investor_prefs.get(user["id"]) or {}
@router.put("/preferences", summary="保存投资方向")
async def set_preferences(
req: PreferenceRequest,
request: Request,
db: Database = Depends(get_db),
actor: dict = Depends(require_roles("investor")),
):
pref = await db.investor_prefs.upsert(actor["id"], req.model_dump(exclude_none=True))
await write_audit(db, action="preference.update", resource="investor", resource_id=actor["id"],
user=actor, request=request)
return pref
@router.get("/projects", summary="项目库")
async def list_projects(
db: Database = Depends(get_db),
_u: dict = Depends(require_roles("investor")),
):
return await db.portal_pages.get("investor", "projects") or {"items": []}
@router.get("/trainings", summary="投融资培训")
async def list_trainings(
db: Database = Depends(get_db),
_u: dict = Depends(require_roles("investor")),
):
return await db.portal_pages.get("investor", "trainings") or {"items": []}
@router.post("/trainings/{training_id}/enroll", summary="培训报名")
async def enroll_training(
training_id: str,
request: Request,
db: Database = Depends(get_db),
actor: dict = Depends(require_roles("investor")),
):
data = await db.portal_pages.get("investor", "trainings") or {"items": []}
item = next((it for it in data.get("items", []) if it.get("title") == training_id), None)
name = (item or {}).get("title", training_id)
enroll = await db.training_enrolls.create(actor["id"], training_id, name)
await write_audit(db, action="training.enroll", resource="training", resource_id=enroll["id"],
detail=name, user=actor, request=request)
return enroll
@router.get("/my-trainings", summary="我的培训")
async def my_trainings(
db: Database = Depends(get_db),
user: dict = Depends(require_roles("investor")),
):
return {"items": await db.training_enrolls.list_for(user["id"])}
@router.get("/portfolio", summary="投资组合")
async def portfolio(
db: Database = Depends(get_db),
_u: dict = Depends(require_roles("investor")),
):
return await db.portal_pages.get("investor", "portfolio") or {"items": []}
@router.post("/projects/{project_id}/intent", summary="发起投资意向/约谈")
async def create_intent(
project_id: str,
req: IntentRequest,
request: Request,
db: Database = Depends(get_db),
actor: dict = Depends(require_roles("investor")),
):
intent = await db.intents.create(
actor["id"], req.project_id or project_id, req.project_name, req.message,
)
await write_audit(db, action="intent.create", resource="investor", resource_id=intent["id"],
detail=req.project_name, user=actor, request=request)
return intent
@router.get("/intents", summary="我的投资意向")
async def list_intents(
db: Database = Depends(get_db),
user: dict = Depends(require_roles("investor")),
):
return {"items": await db.intents.list_for(user["id"])}
@router.get("/roadshows", summary="路演活动列表")
async def list_roadshows(
status: str | None = None,
db: Database = Depends(get_db),
_u: dict = Depends(require_roles(*_ROADSHOW_ROLES)),
):
return {"items": await db.roadshows.list(status=status)}
@router.post("/roadshows", summary="发布路演活动(按权限与范围审核)")
async def create_roadshow(
req: RoadshowCreate,
request: Request,
db: Database = Depends(get_db),
actor: dict = Depends(require_roles(*_ROADSHOW_ROLES)),
):
from ...services.roadshow_service import RoadshowService
rs = await RoadshowService(db).create(req, actor)
await write_audit(db, action="roadshow.publish", resource="roadshow", resource_id=rs["id"],
detail=f"{rs['title']} need_review={rs.get('need_review')}", user=actor, request=request)
return rs
@router.post("/roadshows/{roadshow_id}/register", summary="活动报名")
async def register_roadshow(
roadshow_id: str,
request: Request,
db: Database = Depends(get_db),
actor: dict = Depends(require_roles(*_ROADSHOW_ROLES)),
):
rs = await db.roadshows.get(roadshow_id)
if rs is None or rs["status"] not in ("published", "registering"):
raise HTTPException(status_code=400, detail="活动不可报名")
if rs.get("register_deadline") and rs["register_deadline"] < "2099-01-01":
pass # 演示:不做硬性截止校验
reg = await db.roadshow_regs.create(roadshow_id, actor["id"], role="investor")
return reg