01ef3c9637
- 新建 api/schemas/:auth/agents/admin/ecosystem/investor/opc/operator/org/portals - models.py 移除 15 个 Pydantic DTO(保留 ORM 数据模型层) - 13 个路由内联 DTO 全部迁出,路由瘦身仅剩处理逻辑 - 全量 66 测试通过
190 lines
6.8 KiB
Python
190 lines
6.8 KiB
Python
# -*- 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
|