chore: 汇总提交(园区资料/内容审核/任务相关)+迁移0019/0021及部署编排调整
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
"""资讯审核体系:cover_small 小封面 / priority 优先级
|
||||
|
||||
Revision ID: 0019_content_review
|
||||
Revises: 0018_content_publish
|
||||
Create Date: 2026-08-27
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = "0019_content_review"
|
||||
down_revision = "0018_content_publish"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("content_items", sa.Column("cover_small", sa.String(), nullable=False, server_default=""))
|
||||
op.add_column("content_items", sa.Column("priority", sa.Integer(), nullable=False, server_default="0"))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("content_items", "priority")
|
||||
op.drop_column("content_items", "cover_small")
|
||||
@@ -0,0 +1,38 @@
|
||||
"""园区企业/用户富资料:park_companies + users 加列
|
||||
|
||||
Revision ID: 0021_park_profile
|
||||
Revises: 0020_task_multi_mode
|
||||
Create Date: 2026-08-27
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = "0021_park_profile"
|
||||
down_revision = "0020_task_multi_mode"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 企业丰富资料
|
||||
for col in ["legal_person", "legal_phone", "registered_capital", "company_type",
|
||||
"address", "contact_phone", "founded_at"]:
|
||||
op.add_column("park_companies", sa.Column(col, sa.String(), nullable=False, server_default=""))
|
||||
op.add_column("park_companies", sa.Column("honors", sa.Text(), nullable=False, server_default=""))
|
||||
for col in ["emp_total", "emp_grad", "emp_layoff", "emp_veteran", "emp_migrant"]:
|
||||
op.add_column("park_companies", sa.Column(col, sa.Integer(), nullable=True))
|
||||
# 用户详细资料
|
||||
for col in ["id_card", "ethnicity", "grad_school_major", "grad_time"]:
|
||||
op.add_column("users", sa.Column(col, sa.String(), nullable=False, server_default=""))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
for col in ["grad_time", "grad_school_major", "ethnicity", "id_card"]:
|
||||
op.drop_column("users", col)
|
||||
for col in ["emp_migrant", "emp_veteran", "emp_layoff", "emp_grad", "emp_total"]:
|
||||
op.drop_column("park_companies", col)
|
||||
for col in ["founded_at", "contact_phone", "address", "honors", "company_type",
|
||||
"registered_capital", "legal_phone", "legal_person"]:
|
||||
op.drop_column("park_companies", col)
|
||||
@@ -91,7 +91,10 @@ async def set_user_classification(
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_permission("action:user.manage")),
|
||||
):
|
||||
fields = req.model_dump(exclude_none=True)
|
||||
# 铁律:任何端口不得手动修改用户「所属园区」。affiliation/park_id/park_name 仅经
|
||||
# 入驻申请 / 转园申请(审核通过)回填,此处剔除,只允许改 认证状态/账号类型。
|
||||
fields = {k: v for k, v in req.model_dump(exclude_none=True).items()
|
||||
if k in ("certification_status", "certification_time", "account_type")}
|
||||
updated = await db.users.set_classification(user_id, **fields)
|
||||
if updated is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
@@ -49,6 +49,11 @@ class User(Base):
|
||||
company_avatar: Mapped[str] = mapped_column(String, default="")
|
||||
gender: Mapped[str] = mapped_column(String, default="") # male|female|unknown
|
||||
birthday: Mapped[str] = mapped_column(String, default="") # YYYY-MM-DD
|
||||
# 用户详细资料(参考入园申请表 §一 申请人信息)
|
||||
id_card: Mapped[str] = mapped_column(String, default="") # 身份证号
|
||||
ethnicity: Mapped[str] = mapped_column(String, default="") # 民族
|
||||
grad_school_major: Mapped[str] = mapped_column(String, default="") # 毕业院校及专业
|
||||
grad_time: Mapped[str] = mapped_column(String, default="") # 毕业时间
|
||||
# RBAC
|
||||
role: Mapped[str] = mapped_column(String, default="opc_member") # 业务角色
|
||||
sub_role: Mapped[str] = mapped_column(String, nullable=True) # 政务级/运营方内部
|
||||
@@ -836,12 +841,26 @@ class ParkCompany(Base):
|
||||
founder: Mapped[str] = mapped_column(String, default="")
|
||||
status: Mapped[str] = mapped_column(String, default="applying")
|
||||
employees: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
# 企业丰富资料(参考入园申请表 §二 企业信息)
|
||||
legal_person: Mapped[str] = mapped_column(String, default="") # 法定代表人
|
||||
legal_phone: Mapped[str] = mapped_column(String, default="") # 法人联系电话
|
||||
registered_capital: Mapped[str] = mapped_column(String, default="") # 注册资本(万元)
|
||||
company_type: Mapped[str] = mapped_column(String, default="") # 企业类型/性质
|
||||
honors: Mapped[str] = mapped_column(Text, default="") # 所获政策/荣誉
|
||||
address: Mapped[str] = mapped_column(String, default="") # 企业地址
|
||||
contact_phone: Mapped[str] = mapped_column(String, default="") # 企业联系人电话
|
||||
founded_at: Mapped[str] = mapped_column(String, default="") # 成立时间
|
||||
emp_total: Mapped[int | None] = mapped_column(Integer, nullable=True) # 职工总数
|
||||
emp_grad: Mapped[int | None] = mapped_column(Integer, nullable=True) # 毕业大学生
|
||||
emp_layoff: Mapped[int | None] = mapped_column(Integer, nullable=True) # 失业人员
|
||||
emp_veteran: Mapped[int | None] = mapped_column(Integer, nullable=True) # 退役军人
|
||||
emp_migrant: Mapped[int | None] = mapped_column(Integer, nullable=True) # 返乡农民工
|
||||
# 企业算力(只增不减):折扣% off 模型价;配额为发放给成员的算力额度
|
||||
compute_discount: Mapped[int] = mapped_column(Integer, default=0) # 折扣%(0-100,单调非降)
|
||||
compute_quota: Mapped[int] = mapped_column(Integer, default=0) # 企业算力配额(单调非降/增量)
|
||||
compute_quota_used: Mapped[int] = mapped_column(Integer, default=0) # 已分配/已用配额(统计)
|
||||
engine_group: Mapped[str] = mapped_column(String, default="") # 引擎用户组名(折扣倍率载体)
|
||||
owner_user_id: Mapped[str] = mapped_column(String, default="") # 企业联系人/管理员账号id(可选)
|
||||
owner_user_id: Mapped[str] = mapped_column(String, default="") # 负责人/管理员账号id(必填)
|
||||
created_at: Mapped[str] = mapped_column(String, default="")
|
||||
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ PERMISSIONS = [
|
||||
("menu:admin_plans", "流程启动", "menu", "admin"),
|
||||
("menu:admin_compute", "算力管理", "menu", "admin"),
|
||||
("menu:admin_content", "资讯管理", "menu", "admin"),
|
||||
("menu:admin_content_review", "资讯审核", "menu", "admin"),
|
||||
("menu:admin_tasks", "任务管理", "menu", "admin"),
|
||||
("menu:admin_providers", "服务商管理", "menu", "admin"),
|
||||
("menu:admin_config", "系统配置", "menu", "admin"),
|
||||
@@ -148,7 +149,9 @@ PERMISSIONS = [
|
||||
# 园区(carrier)工作台
|
||||
("menu:park_screens", "屏幕管理", "menu", "carrier"),
|
||||
("menu:park_screen", "大屏控制台", "menu", "carrier"),
|
||||
("menu:park_companies", "入驻企业", "menu", "carrier"),
|
||||
("menu:park_companies", "企业管理", "menu", "carrier"),
|
||||
("menu:park_users", "用户管理", "menu", "carrier"),
|
||||
("menu:park_content", "资讯管理", "menu", "carrier"),
|
||||
("menu:park_kb", "园区知识库", "menu", "carrier"),
|
||||
("menu:park_agent", "园区智能体", "menu", "carrier"),
|
||||
("menu:park_data", "大屏数据", "menu", "carrier"),
|
||||
@@ -162,7 +165,8 @@ ROLE_PERMISSIONS: dict[str, list[str]] = {
|
||||
"opc_member": ["menu:opc_dashboard"],
|
||||
"carrier": [
|
||||
"menu:carrier_dashboard", "action:org.manage",
|
||||
"menu:park_screens", "menu:park_screen", "menu:park_companies", "menu:park_kb",
|
||||
"menu:park_screens", "menu:park_screen", "menu:park_companies", "menu:park_users", "menu:park_content",
|
||||
"menu:park_kb",
|
||||
"menu:park_agent", "menu:park_data", "menu:park_vision",
|
||||
"menu:park_admissions", "menu:park_transfers",
|
||||
],
|
||||
@@ -181,7 +185,7 @@ ROLE_PERMISSIONS: dict[str, list[str]] = {
|
||||
"menu:admin_user_mgmt", "menu:admin_role_mgmt", "menu:audit", "menu:settlements",
|
||||
"menu:admin_courses", "menu:admin_activities", "menu:admin_bookings", "menu:admin_tests",
|
||||
"menu:admin_surveys", "menu:admin_policies", "menu:admin_plans", "menu:admin_compute",
|
||||
"menu:admin_content", "menu:admin_tasks", "menu:admin_providers", "menu:admin_config",
|
||||
"menu:admin_content", "menu:admin_content_review", "menu:admin_tasks", "menu:admin_providers", "menu:admin_config",
|
||||
"menu:admin_park", "menu:admin_park_screens", "menu:opc_cert", "menu:admission", "menu:park_transfer",
|
||||
"action:user.assign_role", "action:user.disable", "action:role.grant_perm",
|
||||
"action:org.manage", "action:data.export", "action:audit.view",
|
||||
|
||||
+3
-1
@@ -1322,7 +1322,9 @@ async def park_user_update(uid: str, body: ParkUserUpdateBody, db=Depends(_carri
|
||||
existing = await db.users.get_by_id(uid)
|
||||
if existing is None or existing.get("park_id") != t["id"]:
|
||||
raise HTTPException(status_code=403, detail="仅可管理本园区用户")
|
||||
u = await db.users.update_profile(uid, body.model_dump(exclude_none=True))
|
||||
# 铁律:账号(username)一旦创建不可再次编辑;此接口仅更新个人资料。
|
||||
fields = {k: v for k, v in body.model_dump(exclude_none=True).items() if k != "username"}
|
||||
u = await db.users.update_profile(uid, fields)
|
||||
return {"ok": True, "user": u}
|
||||
|
||||
|
||||
|
||||
+48
-4
@@ -143,7 +143,14 @@ def _co(r) -> dict:
|
||||
"founder": r.founder, "status": r.status, "employees": r.employees, "created_at": r.created_at,
|
||||
"compute_discount": r.compute_discount, "compute_quota": r.compute_quota,
|
||||
"compute_quota_used": r.compute_quota_used, "engine_group": r.engine_group,
|
||||
"owner_user_id": r.owner_user_id}
|
||||
"owner_user_id": r.owner_user_id,
|
||||
"legal_person": getattr(r, "legal_person", ""), "legal_phone": getattr(r, "legal_phone", ""),
|
||||
"registered_capital": getattr(r, "registered_capital", ""), "company_type": getattr(r, "company_type", ""),
|
||||
"honors": getattr(r, "honors", ""), "address": getattr(r, "address", ""),
|
||||
"contact_phone": getattr(r, "contact_phone", ""), "founded_at": getattr(r, "founded_at", ""),
|
||||
"emp_total": getattr(r, "emp_total", None), "emp_grad": getattr(r, "emp_grad", None),
|
||||
"emp_layoff": getattr(r, "emp_layoff", None), "emp_veteran": getattr(r, "emp_veteran", None),
|
||||
"emp_migrant": getattr(r, "emp_migrant", None)}
|
||||
|
||||
|
||||
def _kb(r) -> dict:
|
||||
@@ -303,11 +310,25 @@ async def list_companies(tenant_id: str) -> list[dict]:
|
||||
return out
|
||||
|
||||
|
||||
_COMPANY_FIELDS = ("name", "zone", "room", "industry", "bio", "founder", "status", "employees",
|
||||
"owner_user_id", "legal_person", "legal_phone", "registered_capital", "company_type",
|
||||
"honors", "address", "contact_phone", "founded_at",
|
||||
"emp_total", "emp_grad", "emp_layoff", "emp_veteran", "emp_migrant")
|
||||
|
||||
|
||||
async def create_company(tenant_id: str, payload: dict) -> dict:
|
||||
row = ParkCompany(id=_new_id("PC"), tenant_id=tenant_id, name=payload.get("name", ""),
|
||||
zone=payload.get("zone", ""), room=payload.get("room", ""), industry=payload.get("industry", ""),
|
||||
bio=payload.get("bio", ""), founder=payload.get("founder", ""),
|
||||
status=payload.get("status", "applying"), employees=payload.get("employees"), created_at=_now())
|
||||
status=payload.get("status", "applying"), employees=payload.get("employees"),
|
||||
legal_person=payload.get("legal_person", ""), legal_phone=payload.get("legal_phone", ""),
|
||||
registered_capital=payload.get("registered_capital", ""), company_type=payload.get("company_type", ""),
|
||||
honors=payload.get("honors", ""), address=payload.get("address", ""),
|
||||
contact_phone=payload.get("contact_phone", ""), founded_at=payload.get("founded_at", ""),
|
||||
emp_total=payload.get("emp_total"), emp_grad=payload.get("emp_grad"),
|
||||
emp_layoff=payload.get("emp_layoff"), emp_veteran=payload.get("emp_veteran"),
|
||||
emp_migrant=payload.get("emp_migrant"), owner_user_id=payload.get("owner_user_id", ""),
|
||||
created_at=_now())
|
||||
async with _get_session() as s:
|
||||
s.add(row)
|
||||
await s.commit()
|
||||
@@ -316,7 +337,7 @@ async def create_company(tenant_id: str, payload: dict) -> dict:
|
||||
|
||||
|
||||
async def update_company(tenant_id: str, cid: str, payload: dict) -> dict | None:
|
||||
fields = {k: v for k, v in payload.items() if v is not None and k in ("name", "zone", "room", "industry", "bio", "founder", "status", "employees", "owner_user_id")}
|
||||
fields = {k: v for k, v in payload.items() if v is not None and k in _COMPANY_FIELDS}
|
||||
if not fields:
|
||||
return None
|
||||
async with _get_session() as s:
|
||||
@@ -331,6 +352,20 @@ async def update_company(tenant_id: str, cid: str, payload: dict) -> dict | None
|
||||
return _co(row)
|
||||
|
||||
|
||||
async def set_company_owner(company_id: str, user_id: str) -> dict | None:
|
||||
"""设置企业负责人:该用户须为企业成员(park_company_id 命中)。"""
|
||||
async with _get_session() as s:
|
||||
u = await s.get(User, user_id)
|
||||
c = await s.get(ParkCompany, company_id)
|
||||
if u is None or c is None:
|
||||
return None
|
||||
if u.park_company_id != company_id:
|
||||
return {"ok": False, "reason": "该用户不隶属此企业,无法设为负责人"}
|
||||
c.owner_user_id = user_id
|
||||
await s.commit()
|
||||
return {"ok": True, "company_id": company_id, "owner_user_id": user_id}
|
||||
|
||||
|
||||
async def delete_company(tenant_id: str, cid: str) -> bool:
|
||||
async with _get_session() as s:
|
||||
r = await s.execute(delete(ParkCompany).where(ParkCompany.id == cid, ParkCompany.tenant_id == tenant_id))
|
||||
@@ -361,18 +396,27 @@ async def company_members(company_id: str) -> list[dict]:
|
||||
"id": r.id, "username": r.username, "nickname": r.nickname or "",
|
||||
"phone": r.phone or "", "role": r.role or "",
|
||||
"certification_status": r.certification_status or "uncertified",
|
||||
"company": r.company or "", "gender": r.gender or "", "birthday": r.birthday or "",
|
||||
"id_card": r.id_card or "", "ethnicity": r.ethnicity or "",
|
||||
"grad_school_major": r.grad_school_major or "", "grad_time": r.grad_time or "",
|
||||
"compute_quota": r.compute_quota or 0, "compute_used_quota": r.compute_used_quota or 0,
|
||||
"created_at": r.created_at or "",
|
||||
} for r in rows]
|
||||
|
||||
|
||||
async def add_company_member(company_id: str, user_id: str) -> dict | None:
|
||||
"""把用户归属到该企业(成员)。"""
|
||||
"""把用户归属到该企业(成员)。
|
||||
|
||||
铁律:仅绑定企业成员关系(park_company_id),绝不改动用户所属园区(park_id)。
|
||||
且仅允许把「已属于本园区」的用户加入本园区企业——跨园区用户须经转园/入驻申请。
|
||||
"""
|
||||
async with _get_session() as s:
|
||||
u = await s.get(User, user_id)
|
||||
c = await s.get(ParkCompany, company_id)
|
||||
if u is None or c is None:
|
||||
return None
|
||||
if u.park_id != c.tenant_id:
|
||||
return {"ok": False, "reason": "该用户不属于本园区,变动园区须经转园/入驻申请"}
|
||||
u.park_company_id = company_id
|
||||
u.updated_at = _now()
|
||||
await s.commit()
|
||||
|
||||
@@ -5,13 +5,13 @@ services:
|
||||
container_name: opc-mysql
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root_pass}
|
||||
MYSQL_ROOT_PASSWORD: sjnxhyjashaywiuwhaja
|
||||
MYSQL_DATABASE: opc
|
||||
MYSQL_USER: opc
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-opc_pass}
|
||||
MYSQL_PASSWORD: jjjsgysyujkwjwgb
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
ports:
|
||||
- "127.0.0.1:3306:3306"
|
||||
- "8091:3306"
|
||||
volumes:
|
||||
- ../../serverdata/mysql-data:/var/lib/mysql
|
||||
networks:
|
||||
|
||||
@@ -6,7 +6,7 @@ services:
|
||||
restart: always
|
||||
command: ["redis-server", "--requirepass", "redis_pass"]
|
||||
ports:
|
||||
- "127.0.0.1:6379:6379"
|
||||
- "8092:6379"
|
||||
volumes:
|
||||
- ../../serverdata/redis-data:/data
|
||||
networks:
|
||||
|
||||
Reference in New Issue
Block a user