feat(incubator): 园区设置+企业/成员信息自动识别接口
- 新增 IncubatorSetting 模型与迁移 0042:开放区域说明/申请注意事项/月报说明 - GET/PUT /incubator/settings(管理方维护,提交方只读) - GET /incubator/company(提交方企业资料,月报/申请表单自动带出) - GET /incubator/company/members(企业绑定成员,联系人自动填入)
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
"""incubator settings (园区级设置)
|
||||
|
||||
Revision ID: 0042_incubator_settings
|
||||
Revises: 0041_direct_messages
|
||||
Create Date: 2026-09-01
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision: str = "0042_incubator_settings"
|
||||
down_revision: Union[str, None] = "0041_direct_messages"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"incubator_settings",
|
||||
sa.Column("id", sa.String(), primary_key=True),
|
||||
sa.Column("tenant_id", sa.String(), nullable=False, index=True),
|
||||
sa.Column("reservation_notice", sa.Text(), nullable=False, default=""),
|
||||
sa.Column("report_notice", sa.Text(), nullable=False, default=""),
|
||||
sa.Column("open_summary", sa.Text(), nullable=False, default=""),
|
||||
sa.Column("updated_by", sa.String(), nullable=False, default=""),
|
||||
sa.Column("updated_at", sa.String(), nullable=False, default=""),
|
||||
sa.UniqueConstraint("tenant_id", name="uq_incubator_setting_tenant"),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("incubator_settings")
|
||||
@@ -104,6 +104,12 @@ class ReviewReservationRequest(BaseModel):
|
||||
record_handler: str = "" # 备案经办人
|
||||
|
||||
|
||||
class SettingsUpdateRequest(BaseModel):
|
||||
reservation_notice: str = "" # 共享区域申请注意事项/须知
|
||||
report_notice: str = "" # 月报填报说明
|
||||
open_summary: str = "" # 开放区域总览说明
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 公共:身份 / 概览
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -482,3 +488,53 @@ async def incubator_finish_reservation(
|
||||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.get("/settings", summary="园区设置(注意事项/开放说明/月报说明)")
|
||||
async def incubator_get_settings(
|
||||
db: Database = Depends(get_db),
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
ident = await resolve_identity(db, user)
|
||||
if not ident.is_manager and not ident.company_id:
|
||||
raise HTTPException(status_code=403, detail="当前账号不属于昆明市大学生创业园,无法使用本应用")
|
||||
return await service.get_settings(db, ident)
|
||||
|
||||
|
||||
@router.put("/settings", summary="更新园区设置(管理方)")
|
||||
async def incubator_update_settings(
|
||||
body: SettingsUpdateRequest,
|
||||
db: Database = Depends(get_db),
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
ident = await resolve_identity(db, user)
|
||||
try:
|
||||
return await service.update_settings(db, ident, body.model_dump())
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
except PermissionError as exc:
|
||||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.get("/company", summary="提交方企业基本信息(表单自动识别填入)")
|
||||
async def incubator_company_profile(
|
||||
db: Database = Depends(get_db),
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
ident = await resolve_identity(db, user)
|
||||
if not ident.is_manager and not ident.company_id:
|
||||
raise HTTPException(status_code=403, detail="当前账号不属于昆明市大学生创业园,无法使用本应用")
|
||||
return await service.get_company_profile(db, ident)
|
||||
|
||||
|
||||
@router.get("/company/members", summary="企业绑定成员列表(联系人自动填入)")
|
||||
async def incubator_company_members(
|
||||
db: Database = Depends(get_db),
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
ident = await resolve_identity(db, user)
|
||||
if not ident.is_manager and not ident.company_id:
|
||||
raise HTTPException(status_code=403, detail="当前账号不属于昆明市大学生创业园,无法使用本应用")
|
||||
return await service.list_company_members(db, ident)
|
||||
|
||||
@@ -25,9 +25,11 @@ from ..infrastructure.models import (
|
||||
CompanyMember,
|
||||
IncubatorAreaReservation,
|
||||
IncubatorMonthlyReport,
|
||||
IncubatorSetting,
|
||||
IncubatorSharedArea,
|
||||
ParkCompany,
|
||||
ParkTenant,
|
||||
User,
|
||||
)
|
||||
from ..infrastructure.repositories import utcnow_iso
|
||||
|
||||
@@ -655,3 +657,123 @@ async def finish_reservation(db, identity: Identity, rsv_id: str) -> dict:
|
||||
r.updated_at = _now()
|
||||
await db.session.commit()
|
||||
return _rsv_row(r)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 园区设置(开放区域说明 / 申请注意事项 / 月报说明)
|
||||
# ===========================================================================
|
||||
def _setting_row(r: IncubatorSetting | None) -> dict:
|
||||
return {
|
||||
"reservation_notice": (r.reservation_notice if r else ""),
|
||||
"report_notice": (r.report_notice if r else ""),
|
||||
"open_summary": (r.open_summary if r else ""),
|
||||
"updated_by": (r.updated_by if r else ""),
|
||||
"updated_at": (r.updated_at if r else ""),
|
||||
}
|
||||
|
||||
|
||||
async def get_settings(db, identity: Identity) -> dict:
|
||||
"""园区级设置:管理方与提交方都可读(管理方见空则给默认引导文案)。"""
|
||||
require_identity(identity)
|
||||
r = (await db.session.execute(
|
||||
select(IncubatorSetting).where(
|
||||
IncubatorSetting.tenant_id == identity.tenant_id)
|
||||
)).scalar_one_or_none()
|
||||
return _setting_row(r)
|
||||
|
||||
|
||||
async def update_settings(db, identity: Identity, data: dict) -> dict:
|
||||
"""更新园区级设置(管理方)。"""
|
||||
if not identity.is_manager:
|
||||
raise PermissionError("仅管理方可维护园区设置")
|
||||
r = (await db.session.execute(
|
||||
select(IncubatorSetting).where(
|
||||
IncubatorSetting.tenant_id == identity.tenant_id)
|
||||
)).scalar_one_or_none()
|
||||
if r is None:
|
||||
r = IncubatorSetting(id=f"set_{secrets.token_hex(8)}",
|
||||
tenant_id=identity.tenant_id)
|
||||
db.session.add(r)
|
||||
if "reservation_notice" in data and data["reservation_notice"] is not None:
|
||||
r.reservation_notice = str(data["reservation_notice"])
|
||||
if "report_notice" in data and data["report_notice"] is not None:
|
||||
r.report_notice = str(data["report_notice"])
|
||||
if "open_summary" in data and data["open_summary"] is not None:
|
||||
r.open_summary = str(data["open_summary"])
|
||||
r.updated_by = identity.user_id
|
||||
r.updated_at = _now()
|
||||
await db.session.commit()
|
||||
return _setting_row(r)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 企业基本信息 / 绑定成员(表单自动识别填入)
|
||||
# ===========================================================================
|
||||
async def get_company_profile(db, identity: Identity) -> dict:
|
||||
"""提交方企业资料:名称/区域/单元/法人/联系人/地址/行业等(申请与月报自动带出)。
|
||||
|
||||
管理方不绑定企业 → 返回空 profile(管理方代申请时需手动指定)。
|
||||
"""
|
||||
require_identity(identity)
|
||||
if not identity.company_id:
|
||||
return {"company_id": "", "company_name": identity.company_name,
|
||||
"zone": "", "room": "", "industry": "",
|
||||
"legal_person": "", "legal_phone": "",
|
||||
"contact_phone": "", "contact_name": "",
|
||||
"address": "", "registered_capital": "",
|
||||
"company_type": "", "founder": "", "founded_at": "",
|
||||
"employees": None, "credit_code": ""}
|
||||
comp = await db.session.get(ParkCompany, identity.company_id)
|
||||
if comp is None:
|
||||
return {"company_id": identity.company_id, "company_name": identity.company_name,
|
||||
"zone": "", "room": "", "industry": "",
|
||||
"legal_person": "", "legal_phone": "",
|
||||
"contact_phone": "", "contact_name": "",
|
||||
"address": "", "registered_capital": "",
|
||||
"company_type": "", "founder": "", "founded_at": "",
|
||||
"employees": None, "credit_code": ""}
|
||||
# 统一信用代码:ParkCompany 无该字段,尝试从该企业最近一次月报带出
|
||||
credit_code = ""
|
||||
latest = (await db.session.execute(
|
||||
select(IncubatorMonthlyReport).where(
|
||||
IncubatorMonthlyReport.company_id == comp.id,
|
||||
IncubatorMonthlyReport.credit_code != "",
|
||||
).order_by(IncubatorMonthlyReport.report_month.desc()).limit(1)
|
||||
)).scalar_one_or_none()
|
||||
if latest is not None:
|
||||
credit_code = latest.credit_code
|
||||
return {
|
||||
"company_id": comp.id, "company_name": comp.name,
|
||||
"zone": comp.zone, "room": comp.room, "industry": comp.industry,
|
||||
"legal_person": comp.legal_person, "legal_phone": comp.legal_phone,
|
||||
"contact_phone": comp.contact_phone, "contact_name": comp.founder or "",
|
||||
"address": comp.address, "registered_capital": comp.registered_capital,
|
||||
"company_type": comp.company_type, "founder": comp.founder,
|
||||
"founded_at": comp.founded_at, "employees": comp.employees,
|
||||
"credit_code": credit_code,
|
||||
}
|
||||
|
||||
|
||||
async def list_company_members(db, identity: Identity) -> dict:
|
||||
"""企业绑定成员列表(联系人自动填入用):姓名/手机/职务/是否管理员。"""
|
||||
require_identity(identity)
|
||||
if not identity.company_id:
|
||||
return {"items": [], "total": 0}
|
||||
rows = (await db.session.execute(
|
||||
select(CompanyMember).where(
|
||||
CompanyMember.company_id == identity.company_id,
|
||||
CompanyMember.status == "active",
|
||||
)
|
||||
)).scalars().all()
|
||||
items = []
|
||||
for m in rows:
|
||||
u = await db.session.get(User, m.user_id)
|
||||
items.append({
|
||||
"user_id": m.user_id,
|
||||
"name": (u.nickname if u else "") or (u.username if u else ""),
|
||||
"phone": (u.phone if u else ""),
|
||||
"account": (u.account if u else ""),
|
||||
"is_admin": bool(m.is_admin),
|
||||
"member_type": m.member_type,
|
||||
})
|
||||
return {"items": items, "total": len(items)}
|
||||
|
||||
@@ -1494,3 +1494,21 @@ class DmMessage(Base):
|
||||
content: Mapped[str] = mapped_column(Text, default="")
|
||||
read_at: Mapped[str] = mapped_column(String, default="")
|
||||
created_at: Mapped[str] = mapped_column(String, default="")
|
||||
|
||||
|
||||
class IncubatorSetting(Base):
|
||||
"""创业园应用园区级设置(申请注意事项 / 开放说明 / 月报说明)。
|
||||
|
||||
一园区一行(tenant_id 唯一)。管理方在应用"园区设置"页维护,
|
||||
提交方读取展示在申请/填报页面顶部。
|
||||
"""
|
||||
__tablename__ = "incubator_settings"
|
||||
__table_args__ = (UniqueConstraint("tenant_id", name="uq_incubator_setting_tenant"),)
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True) # set_<hex>
|
||||
tenant_id: Mapped[str] = mapped_column(String, default="", index=True)
|
||||
reservation_notice: Mapped[str] = mapped_column(Text, default="") # 共享区域申请注意事项/须知
|
||||
report_notice: Mapped[str] = mapped_column(Text, default="") # 月报填报说明
|
||||
open_summary: Mapped[str] = mapped_column(Text, default="") # 开放区域总览说明
|
||||
updated_by: Mapped[str] = mapped_column(String, default="")
|
||||
updated_at: Mapped[str] = mapped_column(String, default="")
|
||||
|
||||
Reference in New Issue
Block a user