feat: 业务层 services 继续抽取(路演/机构)

- RoadshowService:路演发布审核规则(operator 免审/政务范围免审/其余需审)
- OrgService:my_orgs 批量查消除 N+1、成员管理(admin 校验)
- 新增 Repository:Org.list_by_ids、OrgMember.list_for_user/get
- rbac_investor/rbac_org 接线,路由瘦身
This commit is contained in:
2026-08-24 01:58:26 +08:00
parent 413bd29390
commit c82f430986
5 changed files with 117 additions and 20 deletions
+40
View File
@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
"""业务层 · 组织机构服务(成员管理,消除 N+1)。"""
from __future__ import annotations
from fastapi import HTTPException
from ..infrastructure.repositories import Database
class OrgService:
"""机构/组织成员管理。"""
def __init__(self, db: Database):
self.db = db
async def my_orgs(self, user_id: str) -> dict:
"""返回当前账号挂靠的所有机构及成员角色(支持一账号多机构,批量查避免 N+1)。"""
members = await self.db.org_members.list_for_user(user_id)
org_ids = [m["org_id"] for m in members]
orgs = {o["id"]: o for o in await self.db.orgs.list_by_ids(org_ids)} if org_ids else {}
return {"items": [
{"org": orgs[m["org_id"]], "member_role": m["role"], "is_admin": m["is_admin"]}
for m in members if m["org_id"] in orgs
]}
async def add_member(self, org_id: str, username: str, role: str, actor: dict) -> dict:
"""添加机构成员(须机构 admin,用户名解析为 user_id)。"""
org = await self.db.orgs.get(org_id)
if org is None:
raise HTTPException(status_code=404, detail="机构不存在")
actor_members = await self.db.org_members.list_for_org(org_id)
if not any(m["user_id"] == actor["id"] and m["is_admin"] for m in actor_members):
raise HTTPException(status_code=403, detail="仅机构管理员可添加成员")
user = await self.db.users.get_by_username(username)
if user is None:
raise HTTPException(status_code=404, detail="用户不存在")
existing = await self.db.org_members.get(org_id, user["id"])
if existing:
return existing
return await self.db.org_members.add_member(org_id, user["id"], role)
+42
View File
@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
"""业务层 · 路演服务(发布审核规则)。"""
from __future__ import annotations
from fastapi import HTTPException
from ..infrastructure.repositories import Database
class RoadshowService:
"""路演发布:按发布主体与范围判定是否需审核。"""
def __init__(self, db: Database):
self.db = db
def _need_review(self, user: dict, req) -> 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
async def create(self, req, actor: dict) -> dict:
"""创建路演(含审核判定)。"""
if not req.title.strip():
raise HTTPException(status_code=400, detail="标题不能为空")
if req.end_at and req.start_at and req.end_at < req.start_at:
raise HTTPException(status_code=400, detail="结束时间不能早于开始时间")
need_review = self._need_review(actor, req)
fields = {
**req.model_dump(exclude_none=True),
"publisher_id": actor["id"],
"publisher_role": actor.get("role", "investor"),
"status": "submitted" if need_review else "published",
"need_review": need_review,
}
return await self.db.roadshows.create(fields)