# -*- coding: utf-8 -*- """数据访问层(Repository):SQLAlchemy/SQLite 实现。 每个 Repository 对应一张表,只做行级操作,不掺入 HTTP/路由逻辑。 方法签名与原先 JSON 实现保持一致,路由与业务逻辑不变。 """ from __future__ import annotations import json import secrets from datetime import datetime, timezone from sqlalchemy import delete, select from sqlalchemy.orm import Session from . import config from .models import ( Agent, AuditLog, Bid, ContentItem, Contract, Dispute, Escrow, FinanceRecord, InvestmentIntent, InvestorPreference, Message, Notification, OpcProfile, OpcTask, Organization, OrganizationMember, Permission, PortalDashboard, PortalPage, Rating, Region, Roadshow, RoadshowRegistration, Role, RolePermission, ServiceProvider, ServiceReferral, SessionToken, SubsidyApplication, SystemConfig, Task, TrainingEnrollment, User, UserIdentity, ) from .security import hash_password, verify_password USER_TABLE = "users" TOKEN_TABLE = "tokens" AGENTS_TABLE = "agents" # 内置智能体身份(唯一来源在服务端;本地据此同步 workspace) DEFAULT_AGENT_ID = "pine_agents_official_001" QA_AGENT_ID = "pine_agents_official_002" AGENT_SEED = ( { "id": DEFAULT_AGENT_ID, "name": "小园", "description": "默认助手,处理和园区、创业、政策等相关工作", "language": "zh", "model_name": "", "template_type": "default", "deletable": False, "use_fixed_soul": True, }, { "id": QA_AGENT_ID, "name": "问答助手", "description": ( "内置 PineAgents 设置问答助手,本地配置在 `PINEAGENTS_WORKING_DIR` 下," "并提供文档。建议在回答前阅读文件;在此工作区外使用绝对路径编写代码。" ), "language": "zh", "model_name": "", "template_type": "qa", "deletable": True, "use_fixed_soul": False, }, ) PROFILE_FIELDS = ( "username", "nickname", "account", "company", "room", "avatar", "company_avatar", ) # --------------------------------------------------------------------------- # 记录工具 # --------------------------------------------------------------------------- def utcnow_iso() -> str: """当前 UTC 时间,ISO 8601 字符串(如 ``2026-08-02T12:00:00+00:00``)。""" return datetime.now(timezone.utc).isoformat() def new_id(prefix: str) -> str: """生成记录主键,如 ``u_`` / ``sess_``。""" return f"{prefix}_{secrets.token_hex(12)}" # 业务角色 → 端口映射(与 rbac_admin._port_for_role 保持一致) _PORT_FOR_ROLE = { "opc_member": "opc", "carrier": "carrier", "enterprise": "enterprise", "provider": "provider", "government": "government", "operator": "operator", "investor": "investor", "developer": "developer", } def is_expired(expires_at: str) -> bool: """判断 ``expires_at``(ISO 字符串)是否已过期。""" try: return datetime.fromisoformat(expires_at) < datetime.now(timezone.utc) except (ValueError, TypeError): return True def _iso_in(seconds: int) -> str: """返回当前时刻往后 ``seconds`` 秒的 ISO 时间戳。""" return datetime.fromtimestamp( datetime.now(timezone.utc).timestamp() + seconds, tz=timezone.utc, ).isoformat() def _user_to_dict(u: User) -> dict: return { "id": u.id, "username": u.username, "password_hash": u.password_hash, "password_salt": u.password_salt, "nickname": u.nickname, "account": u.account, "company": u.company, "room": u.room, "avatar": u.avatar, "company_avatar": u.company_avatar, "role": u.role, "sub_role": u.sub_role, "org_id": u.org_id, "region_id": u.region_id, "status": u.status, "token_version": u.token_version, "created_at": u.created_at, "updated_at": u.updated_at, } # --------------------------------------------------------------------------- # 用户表 # --------------------------------------------------------------------------- class UserRepository: def __init__(self, session: Session): self.session = session def has_users(self) -> bool: return self.session.scalar(select(User.id).limit(1)) is not None def get_by_username(self, username: str) -> dict | None: u = self.session.scalar( select(User).where(User.username.ilike(username.strip())), ) return _user_to_dict(u) if u else None def get_by_id(self, user_id: str) -> dict | None: u = self.session.get(User, user_id) return _user_to_dict(u) if u else None def list(self) -> list[dict]: return [_user_to_dict(u) for u in self.session.scalars(select(User).order_by(User.created_at))] def create( self, username: str, password: str, *, nickname: str = "", account: str = "", company: str = "", room: str = "", avatar: str = "", company_avatar: str = "", role: str = "opc_member", sub_role: str | None = None, org_id: str | None = None, region_id: str | None = None, ) -> dict: digest, salt = hash_password(password) now = utcnow_iso() u = User( id=new_id("u"), username=username.strip(), password_hash=digest, password_salt=salt, nickname=nickname, account=account, company=company, room=room, avatar=avatar, company_avatar=company_avatar, role=role, sub_role=sub_role, org_id=org_id, region_id=region_id, status="active", token_version=0, created_at=now, updated_at=now, ) self.session.add(u) self.session.commit() return _user_to_dict(u) def update_profile(self, user_id: str, fields: dict) -> dict | None: u = self.session.get(User, user_id) if u is None: return None allowed = {k: fields[k] for k in fields if k in PROFILE_FIELDS} if not allowed: return None for k, v in allowed.items(): setattr(u, k, v) u.updated_at = utcnow_iso() self.session.commit() return _user_to_dict(u) def update_credentials( self, user_id: str, new_username: str | None, new_password: str | None, ) -> dict | None: u = self.session.get(User, user_id) if u is None: return None if new_username is not None: u.username = new_username.strip() if new_password is not None: digest, salt = hash_password(new_password) u.password_hash = digest u.password_salt = salt u.updated_at = utcnow_iso() self.session.commit() return _user_to_dict(u) def verify_password(self, user: dict, password: str) -> bool: return verify_password( password, user.get("password_hash", ""), user.get("password_salt", ""), ) def set_role( self, user_id: str, role: str, sub_role: str | None, org_id: str | None, region_id: str | None, ) -> dict | None: u = self.session.get(User, user_id) if u is None: return None # 同步该用户"主角色"身份(角色改变需对已绑定身份会话生效) new_port = _PORT_FOR_ROLE.get(role, "opc") for ident in self.session.scalars( select(UserIdentity).where(UserIdentity.user_id == user_id) ): if ident.role == u.role: ident.role = role ident.sub_role = sub_role ident.port = new_port ident.org_id = org_id ident.region_id = region_id ident.updated_at = utcnow_iso() u.role = role u.sub_role = sub_role u.org_id = org_id u.region_id = region_id u.token_version += 1 # 使既有令牌失效,角色变更即时生效 u.updated_at = utcnow_iso() self.session.commit() return _user_to_dict(u) def set_status(self, user_id: str, status: str) -> dict | None: u = self.session.get(User, user_id) if u is None: return None u.status = status u.updated_at = utcnow_iso() self.session.commit() return _user_to_dict(u) def bump_token_version(self, user_id: str) -> int: u = self.session.get(User, user_id) if u is None: return 0 u.token_version += 1 u.updated_at = utcnow_iso() self.session.commit() return u.token_version def to_profile(self, user: dict) -> dict: """把用户记录裁剪成对外暴露的资料结构(不含任何密码字段)。""" profile = {field: user.get(field, "") for field in PROFILE_FIELDS} profile["role"] = user.get("role", "opc_member") profile["sub_role"] = user.get("sub_role") profile["org_id"] = user.get("org_id") profile["region_id"] = user.get("region_id") profile["permissions"] = user.get("permissions", []) profile["scope_region_ids"] = user.get("scope_region_ids", []) profile["identity_id"] = user.get("identity_id") profile["port"] = user.get("port") profile["identity_name"] = user.get("identity_name", "") return profile # --------------------------------------------------------------------------- # 令牌表(JWT + sessions 撤销台账) # --------------------------------------------------------------------------- class TokenRepository: def __init__(self, session: Session): self.session = session def create( self, user: dict, *, permissions: list[str], scope_region_ids: list[str], scope_level: str | None, expiry_seconds: int, identity_id: str | None = None, ) -> dict: """签发 JWT 并登记 sessions 行,返回 {token, id, user_id, ...}。""" from .jwt import create_access_token token, jti, expires_at = create_access_token( user, permissions=permissions, scope_region_ids=scope_region_ids, scope_level=scope_level, expiry_seconds=expiry_seconds, identity_id=identity_id, ) now = utcnow_iso() rec = SessionToken( id=jti, jti=jti, token=token, user_id=user["id"], username=user.get("username", ""), created_at=now, expires_at=expires_at, revoked=False, ) self.session.add(rec) self.session.commit() return {"id": jti, "token": token, "user_id": user["id"]} def get(self, token: str) -> dict | None: """按 JWT 解码得到的 jti 查会话行。""" from .jwt import decode_access_token payload = decode_access_token(token) if not payload: return None rec = self.session.scalar( select(SessionToken).where(SessionToken.jti == payload.get("jti")), ) if rec is None: return None return { "id": rec.id, "jti": rec.jti, "token": rec.token, "user_id": rec.user_id, "username": rec.username, "expires_at": rec.expires_at, "revoked": rec.revoked, } def session_valid(self, jti: str, user_id: str, ver: int) -> bool: """JWT 声称的会话与用户版本是否仍有效。""" rec = self.session.scalar( select(SessionToken).where(SessionToken.jti == jti), ) if rec is None or rec.revoked or rec.user_id != user_id: return False if is_expired(rec.expires_at): return False u = self.session.get(User, user_id) return u is not None and u.token_version == ver and u.status == "active" def get_valid_user_id(self, token: str) -> str | None: """JWT 有效(验签、会话未吊销、版本一致、用户启用)时返回 user_id。""" from .jwt import decode_access_token payload = decode_access_token(token) if not payload: return None user_id = payload.get("sub") if not self.session_valid(payload.get("jti", ""), user_id, payload.get("ver", 0)): return None return user_id def revoke(self, token: str) -> bool: rec = self.get(token) if rec is None: return False row = self.session.get(SessionToken, rec["id"]) if row is None: return False row.revoked = True self.session.commit() return True def revoke_all(self, user_id: str | None = None) -> int: """吊销会话(指定用户或全部),并递增其 token_version 使旧 JWT 失效。""" if user_id is not None: affected = self.session.execute( delete(SessionToken).where(SessionToken.user_id == user_id) ).rowcount u = self.session.get(User, user_id) if u is not None: u.token_version += 1 self.session.commit() return affected affected = self.session.execute(delete(SessionToken)).rowcount for u in self.session.scalars(select(User)): u.token_version += 1 self.session.commit() return affected # --------------------------------------------------------------------------- # 角色 / 权限 # --------------------------------------------------------------------------- class RoleRepository: def __init__(self, session: Session): self.session = session def list_roles(self) -> list[dict]: matrix = self.role_matrix() return [ { "id": r.id, "name": r.name, "scope_type": r.scope_type, "description": r.description, "permissions": matrix.get(r.id, []), } for r in self.session.scalars(select(Role).order_by(Role.scope_type, Role.id)) ] def list_permissions(self) -> list[dict]: return [ {"id": p.id, "name": p.name, "category": p.category, "module": p.module} for p in self.session.scalars(select(Permission).order_by(Permission.module, Permission.id)) ] def permissions_for(self, role: str, sub_role: str | None) -> list[str]: """返回该用户(业务角色 + 可选子角色)的全部权限码。 合并 role 与 ``role|sub_role`` 两个角色的权限。 """ ids = [role] if sub_role: ids.append(f"{role}|{sub_role}") return sorted( { pid for pid, in self.session.execute( select(RolePermission.permission_id).where( RolePermission.role_id.in_(ids) ) ) } ) def role_matrix(self) -> dict[str, list[str]]: rows = self.session.execute( select(RolePermission.role_id, RolePermission.permission_id) ).all() matrix: dict[str, list[str]] = {} for role_id, perm_id in rows: matrix.setdefault(role_id, []).append(perm_id) return matrix def role_exists(self, role_id: str) -> bool: return self.session.get(Role, role_id) is not None def set_role_permissions(self, role_id: str, permissions: list[str]) -> list[str]: """重设某角色的权限集合(角色权限配置,仅超管)。""" self.session.execute( delete(RolePermission).where(RolePermission.role_id == role_id) ) for pid in permissions: if self.session.get(Permission, pid) is not None: self.session.add(RolePermission(role_id=role_id, permission_id=pid)) self.session.commit() return self.permissions_for_role(role_id) def permissions_for_role(self, role_id: str) -> list[str]: if "|" in role_id: role, sub = role_id.split("|", 1) return self.permissions_for(role, sub) return self.permissions_for(role_id, None) # --------------------------------------------------------------------------- # 区域(数据范围层级) # --------------------------------------------------------------------------- class RegionRepository: def __init__(self, session: Session): self.session = session def get(self, region_id: str) -> dict | None: r = self.session.get(Region, region_id) return {"id": r.id, "name": r.name, "level": r.level, "parent_id": r.parent_id} if r else None def all(self) -> list[dict]: return [ {"id": r.id, "name": r.name, "level": r.level, "parent_id": r.parent_id} for r in self.session.scalars(select(Region)) ] def level(self, region_id: str) -> str | None: r = self.session.get(Region, region_id) return r.level if r else None def _descendants(self, region_id: str) -> set[str]: """收集某区域的全部后代 id(含自身)。""" result = {region_id} rows = self.session.execute( select(Region.id, Region.parent_id) ).all() children: dict[str, list[str]] = {} for rid, parent in rows: if parent: children.setdefault(parent, []).append(rid) stack = list(children.get(region_id, [])) while stack: cur = stack.pop() if cur in result: continue result.add(cur) stack.extend(children.get(cur, [])) return result def visible_region_ids(self, region_id: str | None) -> list[str]: """数据范围:本域 + 全部后代(上级可看下级)。无区域则返回空。""" if not region_id: return [] return sorted(self._descendants(region_id)) # --------------------------------------------------------------------------- # 组织(企业 / 载体 / 服务商) # --------------------------------------------------------------------------- class OrgRepository: def __init__(self, session: Session): self.session = session def _to_dict(self, o: Organization) -> dict: return { "id": o.id, "name": o.name, "type": o.type, "region_id": o.region_id, "parent_id": o.parent_id, "status": o.status, "created_at": o.created_at, } def get(self, org_id: str) -> dict | None: o = self.session.get(Organization, org_id) return self._to_dict(o) if o else None def all(self) -> list[dict]: return [self._to_dict(o) for o in self.session.scalars(select(Organization))] def list_by_region(self, region_ids: list[str]) -> list[dict]: if not region_ids: return [] return [ self._to_dict(o) for o in self.session.scalars( select(Organization).where(Organization.region_id.in_(region_ids)) ) ] def list_by_parent(self, parent_id: str) -> list[dict]: return [ self._to_dict(o) for o in self.session.scalars( select(Organization).where(Organization.parent_id == parent_id) ) ] # --------------------------------------------------------------------------- # 审计日志 # --------------------------------------------------------------------------- class AuditRepository: def __init__(self, session: Session): self.session = session def add(self, *, action: str, resource: str, resource_id: str = "", detail: str = "", ip: str = "", user_id: str | None = None) -> None: self.session.add( AuditLog( id=new_id("aud"), user_id=user_id, action=action, resource=resource, resource_id=resource_id, detail=detail, ip=ip, created_at=utcnow_iso(), ) ) self.session.commit() def list(self, limit: int = 100, offset: int = 0) -> list[dict]: rows = self.session.scalars( select(AuditLog).order_by(AuditLog.created_at.desc()).limit(limit).offset(offset) ) return [ { "id": a.id, "user_id": a.user_id, "action": a.action, "resource": a.resource, "resource_id": a.resource_id, "detail": a.detail, "ip": a.ip, "created_at": a.created_at, } for a in rows ] # --------------------------------------------------------------------------- # 智能体表(身份记录;按用户隔离) # --------------------------------------------------------------------------- class AgentRepository: def __init__(self, session: Session): self.session = session def _to_dict(self, a: Agent) -> dict: return { "id": a.id, "user_id": a.user_id, "port": a.port, "name": a.name, "description": a.description, "language": a.language, "model_name": a.model_name, "deletable": a.deletable, "use_fixed_soul": a.use_fixed_soul, "created_at": a.created_at, "updated_at": a.updated_at, } def get_by_user(self, user_id: str, port: str | None = None) -> list[dict]: if not port: return [] stmt = select(Agent).where(Agent.user_id == user_id, Agent.port == port) rows = self.session.scalars(stmt.order_by(Agent.created_at)) return [self._to_dict(a) for a in rows] def get(self, agent_id: str, user_id: str, port: str | None = None) -> dict | None: if not port: return None stmt = select(Agent).where(Agent.id == agent_id, Agent.user_id == user_id, Agent.port == port) a = self.session.scalar(stmt) return self._to_dict(a) if a else None def count_by_user(self, user_id: str, port: str | None = None) -> int: if not port: return 0 stmt = select(Agent.id).where(Agent.user_id == user_id, Agent.port == port) return len(list(self.session.scalars(stmt))) def create( self, user_id: str, name: str, *, description: str = "", language: str = "zh", model_name: str = "", agent_id: str | None = None, port: str = "opc", ) -> dict: now = utcnow_iso() a = Agent( id=agent_id or new_id("agent"), user_id=user_id, port=port, name=name.strip(), description=description, language=language, model_name=model_name, deletable=True, use_fixed_soul=False, created_at=now, updated_at=now, ) self.session.add(a) self.session.commit() return self._to_dict(a) def update(self, agent_id: str, user_id: str, fields: dict, port: str | None = None) -> dict | None: if not port: return None stmt = select(Agent).where(Agent.id == agent_id, Agent.user_id == user_id, Agent.port == port) a = self.session.scalar(stmt) if a is None: return None allowed = { k: fields[k] for k in fields if k in ("name", "description", "language", "model_name", "use_fixed_soul") } if not allowed: return None for k, v in allowed.items(): setattr(a, k, v) a.updated_at = utcnow_iso() self.session.commit() return self._to_dict(a) def delete(self, agent_id: str, user_id: str, port: str | None = None) -> bool: if not port: return False stmt = select(Agent).where(Agent.id == agent_id, Agent.user_id == user_id, Agent.port == port) a = self.session.scalar(stmt) if a is None: return False self.session.delete(a) self.session.commit() return True # --------------------------------------------------------------------------- # 任务 # --------------------------------------------------------------------------- class TaskRepository: def __init__(self, session: Session): self.session = session def _to_dict(self, t: Task) -> dict: return { "id": t.id, "title": t.title, "category": t.category, "sub_category": t.sub_category, "description": t.description, "mode": t.mode, "budget_min": t.budget_min, "budget_max": t.budget_max, "deadline": t.deadline, "status": t.status, "publisher_org_id": t.publisher_org_id, "publisher_name": t.publisher_name, "created_at": t.created_at, "updated_at": t.updated_at, } def list(self, status: str | None = None) -> list[dict]: q = select(Task).order_by(Task.created_at.desc()) if status: q = q.where(Task.status == status) return [self._to_dict(t) for t in self.session.scalars(q)] def get(self, task_id: str) -> dict | None: t = self.session.get(Task, task_id) return self._to_dict(t) if t else None def create(self, fields: dict) -> dict: now = utcnow_iso() t = Task(id=new_id("task"), title=fields.get("title", ""), category=fields.get("category", ""), sub_category=fields.get("sub_category", ""), description=fields.get("description", ""), mode=fields.get("mode", "grab"), budget_min=fields.get("budget_min", 0), budget_max=fields.get("budget_max", 0), deadline=fields.get("deadline", ""), status=fields.get("status", "draft"), publisher_org_id=fields.get("publisher_org_id"), publisher_name=fields.get("publisher_name", ""), created_at=now, updated_at=now) self.session.add(t) self.session.commit() return self._to_dict(t) def set_status(self, task_id: str, status: str) -> dict | None: t = self.session.get(Task, task_id) if t is None: return None t.status = status t.updated_at = utcnow_iso() self.session.commit() return self._to_dict(t) # --------------------------------------------------------------------------- # 服务商 # --------------------------------------------------------------------------- class ProviderRepository: def __init__(self, session: Session): self.session = session def _to_dict(self, p: ServiceProvider) -> dict: return { "id": p.id, "name": p.name, "category": p.category, "org_id": p.org_id, "level": p.level, "rating": p.rating, "order_count": p.order_count, "status": p.status, "contact": p.contact, "created_at": p.created_at, "updated_at": p.updated_at, } def list(self, status: str | None = None) -> list[dict]: q = select(ServiceProvider).order_by(ServiceProvider.created_at.desc()) if status: q = q.where(ServiceProvider.status == status) return [self._to_dict(p) for p in self.session.scalars(q)] def create(self, fields: dict) -> dict: now = utcnow_iso() p = ServiceProvider(id=new_id("prov"), name=fields.get("name", ""), category=fields.get("category", ""), org_id=fields.get("org_id"), level=fields.get("level", "certified"), rating=fields.get("rating", 5.0), order_count=fields.get("order_count", 0), status=fields.get("status", "pending"), contact=fields.get("contact", ""), created_at=now, updated_at=now) self.session.add(p) self.session.commit() return self._to_dict(p) def update(self, provider_id: str, fields: dict) -> dict | None: p = self.session.get(ServiceProvider, provider_id) if p is None: return None for k in ("name", "category", "level", "status", "contact"): if k in fields: setattr(p, k, fields[k]) if "rating" in fields: p.rating = fields["rating"] p.updated_at = utcnow_iso() self.session.commit() return self._to_dict(p) # --------------------------------------------------------------------------- # 内容 # --------------------------------------------------------------------------- class ContentRepository: def __init__(self, session: Session): self.session = session def _to_dict(self, c: ContentItem) -> dict: return { "id": c.id, "type": c.type, "title": c.title, "summary": c.summary, "body": c.body, "publisher_id": c.publisher_id, "status": c.status, "created_at": c.created_at, "updated_at": c.updated_at, } def list(self, ctype: str | None = None, status: str | None = None) -> list[dict]: q = select(ContentItem).order_by(ContentItem.created_at.desc()) if ctype: q = q.where(ContentItem.type == ctype) if status: q = q.where(ContentItem.status == status) return [self._to_dict(c) for c in self.session.scalars(q)] def create(self, fields: dict) -> dict: now = utcnow_iso() c = ContentItem(id=new_id("cont"), type=fields.get("type", "news"), title=fields.get("title", ""), summary=fields.get("summary", ""), body=fields.get("body", ""), publisher_id=fields.get("publisher_id"), status=fields.get("status", "draft"), created_at=now, updated_at=now) self.session.add(c) self.session.commit() return self._to_dict(c) def set_status(self, content_id: str, status: str) -> dict | None: c = self.session.get(ContentItem, content_id) if c is None: return None c.status = status c.updated_at = utcnow_iso() self.session.commit() return self._to_dict(c) # --------------------------------------------------------------------------- # 系统配置 # --------------------------------------------------------------------------- class ConfigRepository: def __init__(self, session: Session): self.session = session def all(self) -> list[dict]: return [ {"key": c.key, "value": c.value, "description": c.description, "updated_at": c.updated_at} for c in self.session.scalars(select(SystemConfig).order_by(SystemConfig.key)) ] def get(self, key: str) -> str | None: c = self.session.get(SystemConfig, key) return c.value if c else None def set(self, key: str, value: str, description: str = "") -> dict: c = self.session.get(SystemConfig, key) if c is None: c = SystemConfig(key=key, value=value, description=description, updated_at=utcnow_iso()) self.session.add(c) else: c.value = value if description: c.description = description c.updated_at = utcnow_iso() self.session.commit() return {"key": c.key, "value": c.value, "description": c.description, "updated_at": c.updated_at} # --------------------------------------------------------------------------- # OPC 超级个体(工作台聚合数据) # --------------------------------------------------------------------------- class OpcProfileRepository: """OPC 档案(信用评分等)。""" def __init__(self, session: Session): self.session = session def get(self, user_id: str) -> dict | None: row = self.session.get(OpcProfile, user_id) return self._to_dict(row) if row else None def upsert(self, user_id: str, credit_score: int = 80) -> dict: row = self.session.get(OpcProfile, user_id) if row is None: row = OpcProfile( user_id=user_id, credit_score=credit_score, created_at=utcnow_iso(), updated_at=utcnow_iso(), ) self.session.add(row) else: row.credit_score = credit_score row.updated_at = utcnow_iso() self.session.commit() return self._to_dict(row) @staticmethod def _to_dict(p: OpcProfile) -> dict: return { "user_id": p.user_id, "credit_score": p.credit_score, "updated_at": p.updated_at, } class FinanceRepository: """OPC 财务流水(收入/支出)与月度聚合。""" def __init__(self, session: Session): self.session = session def list_by_user(self, user_id: str, category: str | None = None) -> list[dict]: stmt = select(FinanceRecord).where(FinanceRecord.user_id == user_id) if category: stmt = stmt.where(FinanceRecord.category == category) rows = self.session.scalars(stmt.order_by(FinanceRecord.date.desc())).all() return [self._to_dict(f) for f in rows] def total_income(self, user_id: str) -> int: return sum( f.amount for f in self.session.scalars( select(FinanceRecord).where( FinanceRecord.user_id == user_id, FinanceRecord.category == "income", ) ) ) def monthly_income(self, user_id: str, limit: int = 6) -> list[dict]: """按 YYYY-MM 汇总收入,取最近 N 个月(升序返回)。""" agg: dict[str, int] = {} for f in self.session.scalars( select(FinanceRecord).where( FinanceRecord.user_id == user_id, FinanceRecord.category == "income", ) ): month = (f.date or "")[:7] if not month: continue agg[month] = agg.get(month, 0) + f.amount months = sorted(agg.keys())[-limit:] return [{"month": m, "amount": agg[m]} for m in months] @staticmethod def _to_dict(f: FinanceRecord) -> dict: return { "id": f.id, "user_id": f.user_id, "category": f.category, "amount": f.amount, "date": f.date, "note": f.note, "created_at": f.created_at, } class MessageRepository: """OPC 站内消息。""" def __init__(self, session: Session): self.session = session def recent(self, user_id: str, limit: int = 4) -> list[dict]: rows = self.session.scalars( select(Message) .where(Message.user_id == user_id) .order_by(Message.created_at.desc()) .limit(limit) ).all() return [self._to_dict(m) for m in rows] def list_by_user(self, user_id: str) -> list[dict]: rows = self.session.scalars( select(Message) .where(Message.user_id == user_id) .order_by(Message.created_at.desc()) ).all() return [self._to_dict(m) for m in rows] def unread_count(self, user_id: str) -> int: return len( self.session.scalars( select(Message).where(Message.user_id == user_id, Message.read.is_(False)) ).all() ) @staticmethod def _to_dict(m: Message) -> dict: return { "id": m.id, "user_id": m.user_id, "msg_type": m.msg_type, "title": m.title, "content": m.content, "read": m.read, "created_at": m.created_at, } class OpcTaskRepository: """OPC 个人任务看板。""" def __init__(self, session: Session): self.session = session def list_by_user(self, user_id: str) -> list[dict]: rows = self.session.scalars( select(OpcTask) .where(OpcTask.user_id == user_id) .order_by(OpcTask.created_at.desc()) ).all() return [self._to_dict(t) for t in rows] def counts(self, user_id: str) -> dict: in_progress = 0 completed = 0 for row in self.session.scalars( select(OpcTask).where(OpcTask.user_id == user_id) ): if row.status == "completed": completed += 1 elif row.status in ("in_progress", "urgent"): in_progress += 1 return {"in_progress": in_progress, "completed": completed} @staticmethod def _to_dict(t: OpcTask) -> dict: return { "id": t.id, "user_id": t.user_id, "title": t.title, "status": t.status, "budget": t.budget, "deadline": t.deadline, "progress": t.progress, "created_at": t.created_at, "updated_at": t.updated_at, } # --------------------------------------------------------------------------- # 账号↔端口身份(多身份绑定) # --------------------------------------------------------------------------- class IdentityRepository: """一个账号可绑定的多个端口身份(登录后选择其一进入)。""" def __init__(self, session: Session): self.session = session def _to_dict(self, i: UserIdentity) -> dict: org = self.session.get(Organization, i.org_id) if i.org_id else None region = self.session.get(Region, i.region_id) if i.region_id else None return { "id": i.id, "user_id": i.user_id, "port": i.port, "role": i.role, "sub_role": i.sub_role, "name": i.name, "org_id": i.org_id, "region_id": i.region_id, "org_name": org.name if org else None, "region_name": region.name if region else None, "status": i.status, "created_at": i.created_at, "updated_at": i.updated_at, } def get(self, identity_id: str) -> dict | None: row = self.session.get(UserIdentity, identity_id) return self._to_dict(row) if row else None def get_for_user(self, identity_id: str, user_id: str) -> dict | None: row = self.session.scalar( select(UserIdentity).where( UserIdentity.id == identity_id, UserIdentity.user_id == user_id, ) ) return self._to_dict(row) if row else None def list_for_user(self, user_id: str, active_only: bool = True) -> list[dict]: stmt = select(UserIdentity).where(UserIdentity.user_id == user_id) if active_only: stmt = stmt.where(UserIdentity.status == "active") rows = self.session.scalars(stmt.order_by(UserIdentity.port)).all() return [self._to_dict(i) for i in rows] def create( self, user_id: str, *, port: str, role: str | None = None, sub_role: str | None = None, name: str = "", org_id: str | None = None, region_id: str | None = None, ) -> dict: now = utcnow_iso() row = UserIdentity( id=new_id("ident"), user_id=user_id, port=port, role=role or port, sub_role=sub_role, name=name, org_id=org_id, region_id=region_id, status="active", created_at=now, updated_at=now, ) self.session.add(row) self.session.commit() return self._to_dict(row) def set_status(self, identity_id: str, status: str) -> dict | None: row = self.session.get(UserIdentity, identity_id) if row is None: return None row.status = status row.updated_at = utcnow_iso() self.session.commit() return self._to_dict(row) # --------------------------------------------------------------------------- # 端口工作台(按端口 JSON 载荷) # --------------------------------------------------------------------------- class PortalDashboardRepository: def __init__(self, session: Session): self.session = session def get(self, port: str) -> dict | None: row = self.session.get(PortalDashboard, port) if row is None: return None try: payload = json.loads(row.payload) except (ValueError, TypeError): return {} payload["port"] = row.port payload["updated_at"] = row.updated_at return payload def set(self, port: str, payload: dict) -> dict: row = self.session.get(PortalDashboard, port) now = utcnow_iso() data = json.dumps(payload, ensure_ascii=False) if row is None: row = PortalDashboard(port=port, payload=data, updated_at=now) self.session.add(row) else: row.payload = data row.updated_at = now self.session.commit() return self.get(port) class PortalPageRepository: """端口子页面数据(按 端口+页面 存 JSON 载荷)。""" def __init__(self, session: Session): self.session = session def get(self, port: str, page: str) -> dict | None: row = self.session.get(PortalPage, (port, page)) if row is None: return None try: return json.loads(row.payload) except (ValueError, TypeError): return {} def set(self, port: str, page: str, payload: dict) -> dict: row = self.session.get(PortalPage, (port, page)) now = utcnow_iso() data = json.dumps(payload, ensure_ascii=False) if row is None: row = PortalPage(port=port, page=page, payload=data, updated_at=now) self.session.add(row) else: row.payload = data row.updated_at = now self.session.commit() return self.get(port, page) # --------------------------------------------------------------------------- # 投资人端(偏好 / 路演 / 报名 / 意向) # --------------------------------------------------------------------------- class InvestorPreferenceRepository: def __init__(self, session: Session): self.session = session def get(self, user_id: str) -> dict | None: row = self.session.get(InvestorPreference, user_id) if row is None: return None return { "user_id": row.user_id, "industries": json.loads(row.industries or "[]"), "stage": row.stage, "amount_min": row.amount_min, "amount_max": row.amount_max, "region_id": row.region_id, "updated_at": row.updated_at, } def upsert(self, user_id: str, fields: dict) -> dict: row = self.session.get(InvestorPreference, user_id) now = utcnow_iso() if row is None: row = InvestorPreference(user_id=user_id, updated_at=now) self.session.add(row) if "industries" in fields: row.industries = json.dumps(fields["industries"], ensure_ascii=False) if "stage" in fields: row.stage = fields["stage"] or "" if "amount_min" in fields: row.amount_min = fields["amount_min"] or 0 if "amount_max" in fields: row.amount_max = fields["amount_max"] or 0 if "region_id" in fields: row.region_id = fields["region_id"] row.updated_at = now self.session.commit() return self.get(user_id) class RoadshowRepository: def __init__(self, session: Session): self.session = session def _to_dict(self, r: Roadshow) -> dict: region = self.session.get(Region, r.region_id) if r.region_id else None return { "id": r.id, "publisher_id": r.publisher_id, "publisher_role": r.publisher_role, "title": r.title, "summary": r.summary, "activity_type": r.activity_type, "scope_type": r.scope_type, "region_id": r.region_id, "region_name": region.name if region else None, "start_at": r.start_at, "end_at": r.end_at, "register_deadline": r.register_deadline, "quota": r.quota, "venue": r.venue, "live_url": r.live_url, "status": r.status, "need_review": r.need_review, "review_comment": r.review_comment, "created_at": r.created_at, "updated_at": r.updated_at, } def list(self, status: str | None = None) -> list[dict]: stmt = select(Roadshow).order_by(Roadshow.created_at.desc()) if status: stmt = stmt.where(Roadshow.status == status) return [self._to_dict(r) for r in self.session.scalars(stmt)] def get(self, roadshow_id: str) -> dict | None: row = self.session.get(Roadshow, roadshow_id) return self._to_dict(row) if row else None def create(self, fields: dict) -> dict: now = utcnow_iso() row = Roadshow( id=new_id("rs"), publisher_id=fields.get("publisher_id", ""), publisher_role=fields.get("publisher_role", "investor"), title=fields.get("title", ""), summary=fields.get("summary", ""), activity_type=fields.get("activity_type", "online"), scope_type=fields.get("scope_type", "all"), region_id=fields.get("region_id"), start_at=fields.get("start_at", ""), end_at=fields.get("end_at", ""), register_deadline=fields.get("register_deadline", ""), quota=fields.get("quota", 0), venue=fields.get("venue", ""), live_url=fields.get("live_url", ""), status=fields.get("status", "draft"), need_review=fields.get("need_review", True), created_at=now, updated_at=now, ) self.session.add(row) self.session.commit() return self._to_dict(row) def set_status(self, roadshow_id: str, status: str, review_comment: str = "") -> dict | None: row = self.session.get(Roadshow, roadshow_id) if row is None: return None row.status = status if review_comment: row.review_comment = review_comment row.updated_at = utcnow_iso() self.session.commit() return self._to_dict(row) class RoadshowRegistrationRepository: def __init__(self, session: Session): self.session = session def create(self, roadshow_id: str, user_id: str, role: str = "investor", note: str = "") -> dict: row = RoadshowRegistration( id=new_id("rsreg"), roadshow_id=roadshow_id, user_id=user_id, role=role, status="applying", note=note, created_at=utcnow_iso(), ) self.session.add(row) self.session.commit() return {"id": row.id, "roadshow_id": row.roadshow_id, "user_id": row.user_id, "role": row.role, "status": row.status, "note": row.note} class InvestmentIntentRepository: def __init__(self, session: Session): self.session = session def create(self, investor_id: str, project_id: str, project_name: str, message: str = "") -> dict: row = InvestmentIntent( id=new_id("intent"), investor_id=investor_id, project_id=project_id, project_name=project_name, status="interested", message=message, created_at=utcnow_iso(), updated_at=utcnow_iso(), ) self.session.add(row) self.session.commit() return {"id": row.id, "investor_id": row.investor_id, "project_id": row.project_id, "project_name": row.project_name, "status": row.status, "message": row.message} def list_for(self, investor_id: str) -> list[dict]: rows = self.session.scalars( select(InvestmentIntent).where(InvestmentIntent.investor_id == investor_id) .order_by(InvestmentIntent.created_at.desc()) ).all() return [{"id": r.id, "project_id": r.project_id, "project_name": r.project_name, "status": r.status, "message": r.message, "created_at": r.created_at} for r in rows] class BidRepository: """任务竞标。""" def __init__(self, session: Session): self.session = session def list_for_task(self, task_id: str) -> list[dict]: rows = self.session.scalars( select(Bid).where(Bid.task_id == task_id).order_by(Bid.created_at.desc()) ).all() return [self._to_dict(r) for r in rows] def get(self, bid_id: str) -> dict | None: row = self.session.get(Bid, bid_id) return self._to_dict(row) if row else None def create(self, task_id: str, opc_id: str, opc_name: str, quote: int, plan: str = "") -> dict: row = Bid( id=new_id("bid"), task_id=task_id, opc_id=opc_id, opc_name=opc_name, quote=quote, plan=plan, status="submitted", created_at=utcnow_iso(), updated_at=utcnow_iso(), ) self.session.add(row) self.session.commit() return self._to_dict(row) def set_status(self, bid_id: str, status: str) -> dict | None: row = self.session.get(Bid, bid_id) if row is None: return None row.status = status row.updated_at = utcnow_iso() self.session.commit() return self._to_dict(row) @staticmethod def _to_dict(b: Bid) -> dict: return {"id": b.id, "task_id": b.task_id, "opc_id": b.opc_id, "opc_name": b.opc_name, "quote": b.quote, "plan": b.plan, "status": b.status, "created_at": b.created_at, "updated_at": b.updated_at} class SubsidyRepository: """补贴申报(政务三级审批)。""" def __init__(self, session: Session): self.session = session def list(self, region_ids: list[str] | None = None) -> list[dict]: rows = self.session.scalars(select(SubsidyApplication).order_by(SubsidyApplication.created_at.desc())) out = [] for r in rows: if region_ids and r.region_id and r.region_id not in region_ids: continue out.append(self._to_dict(r)) return out def get(self, aid: str) -> dict | None: row = self.session.get(SubsidyApplication, aid) return self._to_dict(row) if row else None def create(self, fields: dict) -> dict: now = utcnow_iso() row = SubsidyApplication( id=new_id("sub"), opc_id=fields.get("opc_id", ""), opc_name=fields.get("opc_name", ""), title=fields.get("title", ""), amount=fields.get("amount", 0), region_id=fields.get("region_id"), status="applying", created_at=now, updated_at=now, ) self.session.add(row) self.session.commit() return self._to_dict(row) def set_status(self, aid: str, status: str, comment: str = "") -> dict | None: row = self.session.get(SubsidyApplication, aid) if row is None: return None row.status = status if comment: row.comment = comment row.updated_at = utcnow_iso() self.session.commit() return self._to_dict(row) @staticmethod def _to_dict(s: SubsidyApplication) -> dict: return {"id": s.id, "opc_id": s.opc_id, "opc_name": s.opc_name, "title": s.title, "amount": s.amount, "region_id": s.region_id, "status": s.status, "comment": s.comment, "created_at": s.created_at} class ServiceReferralRepository: """载体-服务商引荐。""" def __init__(self, session: Session): self.session = session def list_for(self, carrier_id: str | None = None) -> list[dict]: stmt = select(ServiceReferral).order_by(ServiceReferral.created_at.desc()) if carrier_id: stmt = stmt.where(ServiceReferral.carrier_id == carrier_id) return [self._to_dict(r) for r in self.session.scalars(stmt)] def create(self, fields: dict) -> dict: row = ServiceReferral( id=new_id("ref"), carrier_id=fields.get("carrier_id", ""), provider_id=fields.get("provider_id", ""), provider_name=fields.get("provider_name", ""), opc_id=fields.get("opc_id", ""), opc_name=fields.get("opc_name", ""), status="referred", created_at=utcnow_iso(), updated_at=utcnow_iso(), ) self.session.add(row) self.session.commit() return self._to_dict(row) @staticmethod def _to_dict(r: ServiceReferral) -> dict: return {"id": r.id, "carrier_id": r.carrier_id, "provider_id": r.provider_id, "provider_name": r.provider_name, "opc_id": r.opc_id, "opc_name": r.opc_name, "status": r.status, "created_at": r.created_at} class TrainingEnrollmentRepository: """投融资培训报名。""" def __init__(self, session: Session): self.session = session def list_for(self, user_id: str) -> list[dict]: rows = self.session.scalars( select(TrainingEnrollment).where(TrainingEnrollment.user_id == user_id) ).all() return [{"id": r.id, "training_id": r.training_id, "training_name": r.training_name, "status": r.status, "created_at": r.created_at} for r in rows] def create(self, user_id: str, training_id: str, training_name: str) -> dict: row = TrainingEnrollment( id=new_id("treg"), user_id=user_id, training_id=training_id, training_name=training_name, status="enrolled", created_at=utcnow_iso(), ) self.session.add(row) self.session.commit() return {"id": row.id, "training_id": row.training_id, "training_name": row.training_name, "status": row.status} class EscrowRepository: """任务资金托管/结算。""" def __init__(self, session: Session): self.session = session def list(self, status: str | None = None) -> list[dict]: stmt = select(Escrow).order_by(Escrow.created_at.desc()) if status: stmt = stmt.where(Escrow.status == status) return [self._to_dict(r) for r in self.session.scalars(stmt)] def get(self, escrow_id: str) -> dict | None: row = self.session.get(Escrow, escrow_id) return self._to_dict(row) if row else None def create(self, task_id: str, task_title: str, amount: int, commission: int = 0) -> dict: row = Escrow(id=new_id("esc"), task_id=task_id, task_title=task_title, amount=amount, commission=commission, status="deposited", created_at=utcnow_iso(), updated_at=utcnow_iso()) self.session.add(row) self.session.commit() return self._to_dict(row) def set_status(self, escrow_id: str, status: str) -> dict | None: row = self.session.get(Escrow, escrow_id) if row is None: return None row.status = status row.updated_at = utcnow_iso() self.session.commit() return self._to_dict(row) @staticmethod def _to_dict(e: Escrow) -> dict: return {"id": e.id, "task_id": e.task_id, "task_title": e.task_title, "amount": e.amount, "commission": e.commission, "status": e.status, "created_at": e.created_at} class ContractRepository: def __init__(self, session: Session): self.session = session def get_for_task(self, task_id: str) -> dict | None: row = self.session.scalar(select(Contract).where(Contract.task_id == task_id)) return self._to_dict(row) if row else None def create(self, task_id: str, task_title: str, enterprise_id: str, opc_id: str) -> dict: row = Contract(id=new_id("ct"), task_id=task_id, task_title=task_title, enterprise_id=enterprise_id, opc_id=opc_id, status="signed", content=f"任务《{task_title}》电子合同,双方已签署,资金由平台托管。", created_at=utcnow_iso()) self.session.add(row) self.session.commit() return self._to_dict(row) @staticmethod def _to_dict(c: Contract) -> dict: return {"id": c.id, "task_id": c.task_id, "task_title": c.task_title, "enterprise_id": c.enterprise_id, "opc_id": c.opc_id, "status": c.status, "content": c.content, "created_at": c.created_at} class DisputeRepository: def __init__(self, session: Session): self.session = session def list(self, status: str | None = None) -> list[dict]: stmt = select(Dispute).order_by(Dispute.created_at.desc()) if status: stmt = stmt.where(Dispute.status == status) return [self._to_dict(r) for r in self.session.scalars(stmt)] def create(self, task_id: str, task_title: str, initiator: str, reason: str) -> dict: row = Dispute(id=new_id("disp"), task_id=task_id, task_title=task_title, initiator=initiator, reason=reason, status="opened", created_at=utcnow_iso()) self.session.add(row) self.session.commit() return self._to_dict(row) def set_status(self, dispute_id: str, status: str, resolution: str = "") -> dict | None: row = self.session.get(Dispute, dispute_id) if row is None: return None row.status = status if resolution: row.resolution = resolution self.session.commit() return self._to_dict(row) @staticmethod def _to_dict(d: Dispute) -> dict: return {"id": d.id, "task_id": d.task_id, "task_title": d.task_title, "initiator": d.initiator, "reason": d.reason, "status": d.status, "resolution": d.resolution, "created_at": d.created_at} class RatingRepository: def __init__(self, session: Session): self.session = session def avg_for(self, user_id: str) -> float: rows = self.session.scalars(select(Rating).where(Rating.to_id == user_id)).all() return round(sum(r.score for r in rows) / len(rows), 1) if rows else 5.0 def create(self, task_id: str, from_id: str, to_id: str, score: int, comment: str = "") -> dict: row = Rating(id=new_id("rate"), task_id=task_id, from_id=from_id, to_id=to_id, score=max(1, min(5, score)), comment=comment, created_at=utcnow_iso()) self.session.add(row) self.session.commit() return {"id": row.id, "score": row.score, "comment": row.comment} class NotificationRepository: def __init__(self, session: Session): self.session = session def list_for(self, user_id: str, limit: int = 50) -> list[dict]: rows = self.session.scalars( select(Notification).where(Notification.user_id == user_id) .order_by(Notification.created_at.desc()).limit(limit) ).all() return [{"id": r.id, "type": r.type, "title": r.title, "content": r.content, "read": r.read, "created_at": r.created_at} for r in rows] def unread(self, user_id: str) -> int: return len(self.session.scalars( select(Notification).where(Notification.user_id == user_id, Notification.read.is_(False)) ).all()) def create(self, user_id: str, type: str, title: str, content: str) -> dict: row = Notification(id=new_id("ntf"), user_id=user_id, type=type, title=title, content=content, read=False, created_at=utcnow_iso()) self.session.add(row) self.session.commit() return {"id": row.id, "title": row.title} def mark_read(self, user_id: str) -> int: rows = self.session.scalars(select(Notification).where(Notification.user_id == user_id)).all() for r in rows: r.read = True self.session.commit() return len(rows) # --------------------------------------------------------------------------- # 机构成员(机构主账号 + 机构内子账号) # --------------------------------------------------------------------------- class OrganizationMemberRepository: def __init__(self, session: Session): self.session = session def list_for_org(self, org_id: str) -> list[dict]: rows = self.session.scalars( select(OrganizationMember).where(OrganizationMember.org_id == org_id) ).all() out = [] for r in rows: u = self.session.get(User, r.user_id) out.append({"org_id": r.org_id, "user_id": r.user_id, "username": u.username if u else r.user_id, "nickname": u.nickname if u else "", "role": r.role, "is_admin": r.is_admin, "status": r.status}) return out def is_member(self, org_id: str, user_id: str) -> bool: return self.session.get(OrganizationMember, (org_id, user_id)) is not None def is_admin(self, org_id: str, user_id: str) -> bool: row = self.session.get(OrganizationMember, (org_id, user_id)) return bool(row and row.is_admin) def add_member(self, org_id: str, user_id: str, role: str = "member", is_admin: bool = False) -> dict: row = self.session.get(OrganizationMember, (org_id, user_id)) if row is None: row = OrganizationMember(org_id=org_id, user_id=user_id, role=role, is_admin=is_admin, status="active", joined_at=utcnow_iso()) self.session.add(row) else: row.role = role row.is_admin = is_admin self.session.commit() return {"org_id": org_id, "user_id": user_id, "role": role, "is_admin": is_admin} # --------------------------------------------------------------------------- # 统计 # --------------------------------------------------------------------------- class StatsRepository: """运营端/政务端数据总览(基于现有表聚合)。""" def __init__(self, session: Session): self.session = session def overview(self, region_ids: list[str] | None = None) -> dict: users = self.session.scalars(select(User)).all() if region_ids is not None: scope = set(region_ids) users = [u for u in users if not u.region_id or u.region_id in scope] tasks = self.session.scalars(select(Task)).all() providers = self.session.scalars(select(ServiceProvider)).all() content = self.session.scalars(select(ContentItem)).all() active_users = [u for u in users if u.status == "active"] return { "user_count": len(users), "active_user_count": len(active_users), "task_count": len(tasks), "published_task_count": len([t for t in tasks if t.status == "published"]), "provider_count": len(providers), "active_provider_count": len([p for p in providers if p.status == "active"]), "content_count": len(content), "published_content_count": len([c for c in content if c.status == "published"]), } # --------------------------------------------------------------------------- # 数据库门面 # --------------------------------------------------------------------------- class Database: """持有全部 Repository,统一访问入口。""" def __init__(self, db_url: str | None = None, session: Session | None = None): if session is None: from sqlalchemy.orm import sessionmaker from .db import make_engine url = db_url or config.DATABASE_URL self._engine = make_engine(url) self._session_factory = sessionmaker( bind=self._engine, autoflush=False, expire_on_commit=False, ) self._owns_session = True else: self._engine = None self._session_factory = lambda: session # type: ignore[assignment] self._owns_session = False self.session = self._session_factory() self.users = UserRepository(self.session) self.tokens = TokenRepository(self.session) self.agents = AgentRepository(self.session) self.roles = RoleRepository(self.session) self.orgs = OrgRepository(self.session) self.regions = RegionRepository(self.session) self.audit = AuditRepository(self.session) self.tasks = TaskRepository(self.session) self.providers = ProviderRepository(self.session) self.content = ContentRepository(self.session) self.config = ConfigRepository(self.session) self.identities = IdentityRepository(self.session) self.portal_dashboards = PortalDashboardRepository(self.session) self.portal_pages = PortalPageRepository(self.session) self.opc_profiles = OpcProfileRepository(self.session) self.finance = FinanceRepository(self.session) self.messages = MessageRepository(self.session) self.opc_tasks = OpcTaskRepository(self.session) self.investor_prefs = InvestorPreferenceRepository(self.session) self.roadshows = RoadshowRepository(self.session) self.roadshow_regs = RoadshowRegistrationRepository(self.session) self.intents = InvestmentIntentRepository(self.session) self.bids = BidRepository(self.session) self.subsidies = SubsidyRepository(self.session) self.referrals = ServiceReferralRepository(self.session) self.training_enrolls = TrainingEnrollmentRepository(self.session) self.escrows = EscrowRepository(self.session) self.contracts = ContractRepository(self.session) self.disputes = DisputeRepository(self.session) self.ratings = RatingRepository(self.session) self.notifications = NotificationRepository(self.session) self.org_members = OrganizationMemberRepository(self.session) self.stats = StatsRepository(self.session) def initialize(self) -> None: """建表(本实例的 engine)+ 幂等种子(roles 空时才写)。""" from .seed import seed_data if self._engine is not None: from .db import create_all create_all(self._engine) seed_data(self.session) def close(self) -> None: if self._owns_session: self.session.close()