From 30f0baf07495f6b539bd315f90e77bf578434a20 Mon Sep 17 00:00:00 2001 From: Pine Date: Wed, 26 Aug 2026 15:43:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(opc):=20OPC=E8=AE=A4=E8=AF=81/=E5=9B=AD?= =?UTF-8?q?=E5=8C=BA=E8=BD=AC=E5=9B=AD=20=E6=95=B0=E6=8D=AE=E5=B1=82=20?= =?UTF-8?q?=E2=80=94=20OpcCertification/ParkTransfer=20=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=20+=20=E4=B8=89=E4=B8=AA=20repo=20+=20schemas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - models.py: ParkTenant 加 operator_user_id(园区归属载体账号);新增 OpcCertification/ParkTransfer 两 ORM 类 - repositories.py: OpcCertificationRepository/ParkTransferRepository/ParkAdmissionRepository + Database 挂载 - schemas/opc.py: OpcCertApplyRequest/ParkTransferApplyRequest - schemas/admin.py: OpcCertReviewRequest/ParkAdmissionReviewRequest/ParkTransferReviewRequest --- app/api/schemas/admin.py | 15 +++ app/api/schemas/opc.py | 16 +++ app/infrastructure/models.py | 42 ++++++++ app/infrastructure/repositories.py | 167 +++++++++++++++++++++++++++++ 4 files changed, 240 insertions(+) diff --git a/app/api/schemas/admin.py b/app/api/schemas/admin.py index a84b044..c4c3bbe 100644 --- a/app/api/schemas/admin.py +++ b/app/api/schemas/admin.py @@ -57,4 +57,19 @@ async def _validate_role_assignment(db: Database, actor: dict, role: str, sub_ro ) +class OpcCertReviewRequest(BaseModel): + status: str = "certified" # certified | rejected + comment: str = "" + + +class ParkAdmissionReviewRequest(BaseModel): + status: str = "approved" # approved | rejected | reviewing + comment: str = "" + + +class ParkTransferReviewRequest(BaseModel): + status: str = "approved" # approved | rejected | reviewing + comment: str = "" + + diff --git a/app/api/schemas/opc.py b/app/api/schemas/opc.py index 76b82a0..29fd5f1 100644 --- a/app/api/schemas/opc.py +++ b/app/api/schemas/opc.py @@ -40,4 +40,20 @@ class FinanceRecordCreate(BaseModel): note: str = "" +class OpcCertApplyRequest(BaseModel): + real_name: str = "" + gender: str = "" + address: str = "" + industry: str = "" + ability: str = "" + phone: str = "" + docs_json: str = "{}" + + +class ParkTransferApplyRequest(BaseModel): + to_park_id: str = "" + to_park_name: str = "" + reason: str = "" + + diff --git a/app/infrastructure/models.py b/app/infrastructure/models.py index 88b7051..2309093 100644 --- a/app/infrastructure/models.py +++ b/app/infrastructure/models.py @@ -802,6 +802,7 @@ class ParkTenant(Base): status: Mapped[str] = mapped_column(String, default="active") data_json: Mapped[str] = mapped_column(Text, default="{}") agent_json: Mapped[str] = mapped_column(Text, default="{}") + operator_user_id: Mapped[str] = mapped_column(String, default="") # 园区归属载体账号,园区端据此只审本园区 created_at: Mapped[str] = mapped_column(String, default="") @@ -878,3 +879,44 @@ class ParkScreen(Base): code_expires: Mapped[str] = mapped_column(String, default="") # 码过期时间(ISO) bound_at: Mapped[str] = mapped_column(String, default="") created_at: Mapped[str] = mapped_column(String, default="") + + +class OpcCertification(Base): + """OPC 认证申请表(用户提交资料 → 运营方审核)。""" + __tablename__ = "opc_certifications" + id: Mapped[str] = mapped_column(String, primary_key=True) + user_id: Mapped[str] = mapped_column(String, default="", index=True) + username: Mapped[str] = mapped_column(String, default="") + real_name: Mapped[str] = mapped_column(String, default="") + gender: Mapped[str] = mapped_column(String, default="") + address: Mapped[str] = mapped_column(String, default="") + industry: Mapped[str] = mapped_column(String, default="") + ability: Mapped[str] = mapped_column(Text, default="") + phone: Mapped[str] = mapped_column(String, default="") + docs_json: Mapped[str] = mapped_column(Text, default="{}") + status: Mapped[str] = mapped_column(String, default="pending") + review_comment: Mapped[str] = mapped_column(String, default="") + reviewed_by: Mapped[str] = mapped_column(String, default="") + reviewed_at: Mapped[str] = mapped_column(String, default="") + created_at: Mapped[str] = mapped_column(String, default="") + updated_at: Mapped[str] = mapped_column(String, default="") + + +class ParkTransfer(Base): + """OPC 转园申请表(园区 OPC 发起 → 园区/平台审核)。""" + __tablename__ = "park_transfers" + id: Mapped[str] = mapped_column(String, primary_key=True) + user_id: Mapped[str] = mapped_column(String, default="", index=True) + username: Mapped[str] = mapped_column(String, default="") + from_park_id: Mapped[str] = mapped_column(String, default="") + from_park_name: Mapped[str] = mapped_column(String, default="") + to_park_id: Mapped[str] = mapped_column(String, default="") + to_park_name: Mapped[str] = mapped_column(String, default="") + reason: Mapped[str] = mapped_column(Text, default="") + status: Mapped[str] = mapped_column(String, default="pending") + review_comment: Mapped[str] = mapped_column(String, default="") + reviewed_by: Mapped[str] = mapped_column(String, default="") + reviewed_at: Mapped[str] = mapped_column(String, default="") + created_at: Mapped[str] = mapped_column(String, default="") + updated_at: Mapped[str] = mapped_column(String, default="") + diff --git a/app/infrastructure/repositories.py b/app/infrastructure/repositories.py index e9eb308..a80606e 100644 --- a/app/infrastructure/repositories.py +++ b/app/infrastructure/repositories.py @@ -30,6 +30,9 @@ from .models import ( Notification, OpcProfile, OpcTask, + OpcCertification, + ParkAdmission, + ParkTransfer, Organization, OrganizationMember, Permission, @@ -2227,6 +2230,9 @@ class Database: self.finance = FinanceRepository(self.session) self.messages = MessageRepository(self.session) self.opc_tasks = OpcTaskRepository(self.session) + self.opc_certifications = OpcCertificationRepository(self.session) + self.park_transfers = ParkTransferRepository(self.session) + self.park_admissions = ParkAdmissionRepository(self.session) self.investor_prefs = InvestorPreferenceRepository(self.session) self.roadshows = RoadshowRepository(self.session) self.roadshow_regs = RoadshowRegistrationRepository(self.session) @@ -2255,3 +2261,164 @@ class Database: async def close(self) -> None: if self._owns_session: await self.session.close() + + +# --------------------------------------------------------------------------- +# OPC 认证申请表 +# --------------------------------------------------------------------------- +class OpcCertificationRepository: + def __init__(self, session): + self.session = session + + def _to_dict(self, c): + return { + "id": c.id, "user_id": c.user_id, "username": c.username, + "real_name": c.real_name, "gender": c.gender, "address": c.address, + "industry": c.industry, "ability": c.ability, "phone": c.phone, + "docs_json": c.docs_json, "status": c.status, + "review_comment": c.review_comment, "reviewed_by": c.reviewed_by, + "reviewed_at": c.reviewed_at, "created_at": c.created_at, "updated_at": c.updated_at, + } + + async def list(self, status=None): + q = select(OpcCertification).order_by(OpcCertification.created_at.desc()) + if status: + q = q.where(OpcCertification.status == status) + return [self._to_dict(c) for c in await self.session.scalars(q)] + + async def get(self, cert_id): + c = await self.session.get(OpcCertification, cert_id) + return self._to_dict(c) if c else None + + async def by_user(self, user_id): + row = await self.session.scalar( + select(OpcCertification).where(OpcCertification.user_id == user_id) + .order_by(OpcCertification.created_at.desc()).limit(1)) + return self._to_dict(row) if row else None + + async def create(self, fields, user_id, username): + now = utcnow_iso() + c = OpcCertification(id=new_id("opccert"), user_id=user_id, username=username, + real_name=fields.get("real_name", ""), gender=fields.get("gender", ""), + address=fields.get("address", ""), industry=fields.get("industry", ""), + ability=fields.get("ability", ""), phone=fields.get("phone", ""), + docs_json=fields.get("docs_json", "{}"), status=fields.get("status", "pending"), + created_at=now, updated_at=now) + self.session.add(c); await self.session.commit() + return self._to_dict(c) + + async def set_status(self, cert_id, status, reviewer="", comment=""): + c = await self.session.get(OpcCertification, cert_id) + if c is None: + return None + c.status = status + if comment: + c.review_comment = comment + if reviewer: + c.reviewed_by = reviewer; c.reviewed_at = utcnow_iso() + c.updated_at = utcnow_iso() + await self.session.commit() + return self._to_dict(c) + + +# --------------------------------------------------------------------------- +# OPC 转园申请表 +# --------------------------------------------------------------------------- +class ParkTransferRepository: + def __init__(self, session): + self.session = session + + def _to_dict(self, t): + return { + "id": t.id, "user_id": t.user_id, "username": t.username, + "from_park_id": t.from_park_id, "from_park_name": t.from_park_name, + "to_park_id": t.to_park_id, "to_park_name": t.to_park_name, + "reason": t.reason, "status": t.status, + "review_comment": t.review_comment, "reviewed_by": t.reviewed_by, + "reviewed_at": t.reviewed_at, "created_at": t.created_at, "updated_at": t.updated_at, + } + + async def list(self, status=None, park_id=None): + q = select(ParkTransfer).order_by(ParkTransfer.created_at.desc()) + if status: + q = q.where(ParkTransfer.status == status) + if park_id: + q = q.where((ParkTransfer.from_park_id == park_id) | (ParkTransfer.to_park_id == park_id)) + return [self._to_dict(t) for t in await self.session.scalars(q)] + + async def get(self, transfer_id): + t = await self.session.get(ParkTransfer, transfer_id) + return self._to_dict(t) if t else None + + async def by_user(self, user_id): + row = await self.session.scalar( + select(ParkTransfer).where(ParkTransfer.user_id == user_id) + .order_by(ParkTransfer.created_at.desc()).limit(1)) + return self._to_dict(row) if row else None + + async def create(self, fields, user_id, username): + now = utcnow_iso() + t = ParkTransfer(id=new_id("ptrans"), user_id=user_id, username=username, + from_park_id=fields.get("from_park_id", ""), from_park_name=fields.get("from_park_name", ""), + to_park_id=fields.get("to_park_id", ""), to_park_name=fields.get("to_park_name", ""), + reason=fields.get("reason", ""), status=fields.get("status", "pending"), + created_at=now, updated_at=now) + self.session.add(t); await self.session.commit() + return self._to_dict(t) + + async def set_status(self, transfer_id, status, reviewer="", comment=""): + t = await self.session.get(ParkTransfer, transfer_id) + if t is None: + return None + t.status = status + if comment: + t.review_comment = comment + if reviewer: + t.reviewed_by = reviewer; t.reviewed_at = utcnow_iso() + t.updated_at = utcnow_iso() + await self.session.commit() + return self._to_dict(t) + + +# --------------------------------------------------------------------------- +# 园区入驻申请(平台 admin/园区端视图复用) +# --------------------------------------------------------------------------- +class ParkAdmissionRepository: + def __init__(self, session): + self.session = session + + def _to_dict(self, a): + return { + "id": a.id, "tenant_id": a.tenant_id, "tenant_name": a.tenant_name, + "user_id": a.user_id, "username": a.username, + "applicant_name": a.applicant_name, "contact_phone": a.contact_phone, + "status": a.status, "form_json": a.form_json, "docs_json": a.docs_json, + "source": a.source, "review_comment": a.review_comment, + "reviewed_by": a.reviewed_by, "reviewed_at": a.reviewed_at, + "created_at": a.created_at, "updated_at": a.updated_at, + } + + async def list(self, status=None, tenant_id=None): + q = select(ParkAdmission).order_by(ParkAdmission.created_at.desc()) + if status: + q = q.where(ParkAdmission.status == status) + if tenant_id: + q = q.where(ParkAdmission.tenant_id == tenant_id) + return [self._to_dict(a) for a in await self.session.scalars(q)] + + async def get(self, admission_id): + a = await self.session.get(ParkAdmission, admission_id) + return self._to_dict(a) if a else None + + async def set_status(self, admission_id, status, reviewer="", comment=""): + a = await self.session.get(ParkAdmission, admission_id) + if a is None: + return None + a.status = status + if comment: + a.review_comment = comment + if reviewer: + a.reviewed_by = reviewer; a.reviewed_at = utcnow_iso() + a.updated_at = utcnow_iso() + await self.session.commit() + return self._to_dict(a)