feat(opc): OPC认证/入园/转园 端点 + 园区端方案B审核 + 迁移0013
- rbac_opc.py: /opc/certification/apply|mine、/opc/park-transfer/apply|mine - rbac_admin.py: platform operator 兜底 list/review (opc-certifications/park-admissions/park-transfers),通过回填 users.set_classification - park/routers.py: carrier 角色凭 operator_user_id 定位本园区,/park/api park-admissions|transfers view+review - park/tenants.py: bind_admin 写 operator_user_id + find_by_operator_user_id - 迁移 0013: opc_certifications + park_transfers + park_tenants.operator_user_id
This commit is contained in:
@@ -8,6 +8,7 @@ from pydantic import BaseModel
|
||||
from ..dependencies import get_db
|
||||
from ..schemas.admin import SetUserRoleRequest, SetUserStatusRequest, UserClassificationRequest
|
||||
from ..schemas.admin import UserCreateRequest, RolePermissionRequest, UpdateUserRequest
|
||||
from ..schemas.admin import OpcCertReviewRequest, ParkAdmissionReviewRequest, ParkTransferReviewRequest
|
||||
from ...rbac import require_permission, require_roles, write_audit
|
||||
from ...infrastructure.repositories import Database
|
||||
from ...services import compute_client
|
||||
@@ -204,3 +205,115 @@ async def list_audit_logs(
|
||||
_user: dict = Depends(require_permission("action:audit.view")),
|
||||
):
|
||||
return await db.audit.list(limit=min(limit, 500), offset=offset)
|
||||
|
||||
|
||||
async def _apply_admission_user(db, adm, status):
|
||||
"""入园/转园审核通过后回填 users 的园区归属。"""
|
||||
if status not in ("approved", "certified"):
|
||||
return
|
||||
uid = (adm or {}).get("user_id")
|
||||
if not uid:
|
||||
return
|
||||
await db.users.set_classification(uid, affiliation="park",
|
||||
park_id=(adm or {}).get("tenant_id", ""),
|
||||
park_name=(adm or {}).get("tenant_name", ""))
|
||||
|
||||
|
||||
# ==================== OPC 认证 / 入园 / 转园 审核(平台 operator 兜底) ====================
|
||||
|
||||
@router.get("/opc-certifications", summary="OPC 认证申请列表(平台)")
|
||||
async def list_opc_certifications(
|
||||
status: str = "",
|
||||
db: Database = Depends(get_db),
|
||||
_role: dict = Depends(require_roles("operator")),
|
||||
_perm: dict = Depends(require_permission("menu:admin_user_mgmt")),
|
||||
):
|
||||
return {"items": await db.opc_certifications.list(status or None)}
|
||||
|
||||
|
||||
@router.post("/opc-certifications/{cert_id}/review", summary="审核 OPC 认证申请")
|
||||
async def review_opc_certification(
|
||||
cert_id: str,
|
||||
req: OpcCertReviewRequest,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_permission("action:user.manage")),
|
||||
):
|
||||
cert = await db.opc_certifications.get(cert_id)
|
||||
if cert is None:
|
||||
raise HTTPException(status_code=404, detail="认证申请不存在")
|
||||
if req.status not in ("certified", "rejected"):
|
||||
raise HTTPException(status_code=400, detail="状态仅支持 certified/rejected")
|
||||
updated = await db.opc_certifications.set_status(cert_id, req.status,
|
||||
reviewer=actor.get("id", ""), comment=req.comment)
|
||||
if cert.get("user_id"):
|
||||
u = await db.users.get_by_id(cert["user_id"])
|
||||
await db.users.set_classification(cert["user_id"],
|
||||
certification_status=req.status, affiliation=(u or {}).get("affiliation") or "independent")
|
||||
await write_audit(db, action="opc.cert_review", resource="opc_certification",
|
||||
resource_id=cert_id, detail=f"status={req.status}", user=actor, request=request)
|
||||
return {"ok": True, "certification": updated}
|
||||
|
||||
|
||||
@router.get("/park-admissions", summary="园区入驻申请列表(平台)")
|
||||
async def list_park_admissions(
|
||||
status: str = "",
|
||||
db: Database = Depends(get_db),
|
||||
_role: dict = Depends(require_roles("operator")),
|
||||
_perm: dict = Depends(require_permission("menu:admin_user_mgmt")),
|
||||
):
|
||||
return {"items": await db.park_admissions.list(status or None)}
|
||||
|
||||
|
||||
@router.post("/park-admissions/{aid}/review", summary="园区入驻申请审核(平台兜底)")
|
||||
async def review_park_admission(
|
||||
aid: str,
|
||||
req: ParkAdmissionReviewRequest,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_permission("action:user.manage")),
|
||||
):
|
||||
adm = await db.park_admissions.get(aid)
|
||||
if adm is None:
|
||||
raise HTTPException(status_code=404, detail="入驻申请不存在")
|
||||
if req.status not in ("approved", "rejected", "reviewing"):
|
||||
raise HTTPException(status_code=400, detail="状态仅支持 approved/rejected/reviewing")
|
||||
updated = await db.park_admissions.set_status(aid, req.status,
|
||||
reviewer=actor.get("id", ""), comment=req.comment)
|
||||
await _apply_admission_user(db, adm, req.status)
|
||||
await write_audit(db, action="opc.admission_review", resource="park_admission",
|
||||
resource_id=aid, detail=f"status={req.status}", user=actor, request=request)
|
||||
return {"ok": True, "admission": updated}
|
||||
|
||||
|
||||
@router.get("/park-transfers", summary="OPC 转园申请列表(平台)")
|
||||
async def list_park_transfers(
|
||||
status: str = "",
|
||||
db: Database = Depends(get_db),
|
||||
_role: dict = Depends(require_roles("operator")),
|
||||
_perm: dict = Depends(require_permission("menu:admin_user_mgmt")),
|
||||
):
|
||||
return {"items": await db.park_transfers.list(status or None)}
|
||||
|
||||
|
||||
@router.post("/park-transfers/{tid}/review", summary="OPC 转园申请审核(平台兜底)")
|
||||
async def review_park_transfer(
|
||||
tid: str,
|
||||
req: ParkTransferReviewRequest,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_permission("action:user.manage")),
|
||||
):
|
||||
tr = await db.park_transfers.get(tid)
|
||||
if tr is None:
|
||||
raise HTTPException(status_code=404, detail="转园申请不存在")
|
||||
if req.status not in ("approved", "rejected", "reviewing"):
|
||||
raise HTTPException(status_code=400, detail="状态仅支持 approved/rejected/reviewing")
|
||||
updated = await db.park_transfers.set_status(tid, req.status,
|
||||
reviewer=actor.get("id", ""), comment=req.comment)
|
||||
if req.status == "approved":
|
||||
await db.users.set_classification(tr["user_id"], affiliation="park",
|
||||
park_id=tr.get("to_park_id", ""), park_name=tr.get("to_park_name", ""))
|
||||
await write_audit(db, action="opc.park_transfer_review", resource="park_transfer",
|
||||
resource_id=tid, detail=f"status={req.status}", user=actor, request=request)
|
||||
return {"ok": True, "transfer": updated}
|
||||
|
||||
@@ -386,3 +386,92 @@ async def opc_compute_tokens_delete(token_id: int, user: dict = Depends(require_
|
||||
except compute_client.ComputeError as exc:
|
||||
raise HTTPException(status_code=502, detail=f"算力引擎对接失败: {exc}") from exc
|
||||
return {"ok": True, "token_id": token_id}
|
||||
|
||||
|
||||
# ── OPC 认证 ─────────────────────────────────────────────────────────────
|
||||
|
||||
@router.post("/certification/apply", summary="提交 OPC 认证申请")
|
||||
async def opc_cert_apply(
|
||||
req: OpcCertApplyRequest,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
user: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
"""当前 OPC 提交认证资料 → 状态置 pending,待运营方审核。"""
|
||||
me = await db.users.get_by_id(user["id"])
|
||||
if me is None:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
if me.get("certification_status") == "certified":
|
||||
raise HTTPException(status_code=400, detail="当前账号已是认证OPC,无需重复申请")
|
||||
if me.get("certification_status") == "pending":
|
||||
raise HTTPException(status_code=400, detail="认证申请审核中,请勿重复提交")
|
||||
applied = await db.opc_certifications.by_user(user["id"])
|
||||
if applied and applied.get("status") in ("pending", "reviewing"):
|
||||
raise HTTPException(status_code=400, detail="已有待审核的认证申请,请勿重复提交")
|
||||
cert = await db.opc_certifications.create({
|
||||
"real_name": req.real_name, "gender": req.gender, "address": req.address,
|
||||
"industry": req.industry, "ability": req.ability, "phone": req.phone,
|
||||
"docs_json": req.docs_json or "{}",
|
||||
}, user["id"], me.get("username", ""))
|
||||
await write_audit(
|
||||
action="opc.cert_apply", resource="opc_certification", resource_id=cert["id"],
|
||||
detail=f"real_name={req.real_name} industry={req.industry}",
|
||||
user=user, request=request,
|
||||
)
|
||||
return {"ok": True, "certification": cert}
|
||||
|
||||
|
||||
@router.get("/certification/mine", summary="我的 OPC 认证状态")
|
||||
async def opc_cert_mine(
|
||||
db: Database = Depends(get_db),
|
||||
user: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
me = await db.users.get_by_id(user["id"])
|
||||
cert = await db.opc_certifications.by_user(user["id"])
|
||||
return {
|
||||
"certification_status": (me or {}).get("certification_status", "uncertified"),
|
||||
"certification": cert,
|
||||
}
|
||||
|
||||
|
||||
# ── OPC 转园 ─────────────────────────────────────────────────────────────
|
||||
|
||||
@router.post("/park-transfer/apply", summary="发起 OPC 转园申请")
|
||||
async def opc_park_transfer_apply(
|
||||
req: ParkTransferApplyRequest,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
user: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
"""园区 OPC 申请转入目标园区(原园区自动取当前 park 归属,经园区/平台审核)。"""
|
||||
me = await db.users.get_by_id(user["id"])
|
||||
if me is None:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
if me.get("affiliation") != "park" or not me.get("park_id"):
|
||||
raise HTTPException(status_code=400, detail="仅园区 OPC 可发起转园(需先归属园区)")
|
||||
if not req.to_park_id:
|
||||
raise HTTPException(status_code=400, detail="请选择目标园区")
|
||||
if req.to_park_id == me.get("park_id"):
|
||||
raise HTTPException(status_code=400, detail="目标园区与当前园区相同")
|
||||
existing = await db.park_transfers.by_user(user["id"])
|
||||
if existing and existing.get("status") in ("pending", "reviewing"):
|
||||
raise HTTPException(status_code=400, detail="已有待审核的转园申请,请勿重复提交")
|
||||
transfer = await db.park_transfers.create({
|
||||
"to_park_id": req.to_park_id, "to_park_name": req.to_park_name,
|
||||
"reason": req.reason,
|
||||
"from_park_id": me.get("park_id", ""), "from_park_name": me.get("park_name", ""),
|
||||
}, user["id"], me.get("username", ""))
|
||||
await write_audit(
|
||||
action="opc.park_transfer_apply", resource="park_transfer", resource_id=transfer["id"],
|
||||
detail=f"from={me.get('park_name','')} to={req.to_park_name}",
|
||||
user=user, request=request,
|
||||
)
|
||||
return {"ok": True, "transfer": transfer}
|
||||
|
||||
|
||||
@router.get("/park-transfer/mine", summary="我的转园申请")
|
||||
async def opc_park_transfer_mine(
|
||||
db: Database = Depends(get_db),
|
||||
user: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
return {"transfer": await db.park_transfers.by_user(user["id"])}
|
||||
|
||||
Reference in New Issue
Block a user