微信小程序页面迁移校验之前 P5任务处理之前
This commit is contained in:
214
apps/backend/app/routers/admin_applications.py
Normal file
214
apps/backend/app/routers/admin_applications.py
Normal file
@@ -0,0 +1,214 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
管理端审核路由 —— 申请列表、详情、批准、拒绝。
|
||||
|
||||
端点清单:
|
||||
- GET /api/admin/applications — 查询申请列表(可按 status 过滤)
|
||||
- GET /api/admin/applications/{id} — 查询申请详情 + 候选匹配
|
||||
- POST /api/admin/applications/{id}/approve — 批准申请
|
||||
- POST /api/admin/applications/{id}/reject — 拒绝申请
|
||||
|
||||
所有端点要求 JWT + site_admin 或 tenant_admin 角色。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
|
||||
from app.auth.dependencies import CurrentUser
|
||||
from app.database import get_connection
|
||||
from app.middleware.permission import require_permission
|
||||
from app.schemas.xcx_auth import (
|
||||
ApplicationResponse,
|
||||
ApproveRequest,
|
||||
MatchCandidate,
|
||||
RejectRequest,
|
||||
)
|
||||
from app.services.application import approve_application, reject_application
|
||||
from app.services.matching import find_candidates
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/admin", tags=["管理端审核"])
|
||||
|
||||
|
||||
# ── 管理端需要 site_admin 或 tenant_admin 权限 ─────────────
|
||||
# require_permission() 不检查具体 permission code,
|
||||
# 但会验证 status=approved;管理端路由额外在依赖中检查角色。
|
||||
# 设计文档要求 site_admin / tenant_admin 角色,
|
||||
# 这里通过检查 CurrentUser.roles 实现。
|
||||
|
||||
|
||||
def _require_admin():
|
||||
"""
|
||||
管理端依赖:要求用户 status=approved 且角色包含 site_admin 或 tenant_admin。
|
||||
|
||||
复用 require_permission()(无具体权限码 → 仅检查 approved),
|
||||
再额外校验角色列表。
|
||||
"""
|
||||
|
||||
async def _dependency(
|
||||
user: CurrentUser = Depends(require_permission()),
|
||||
) -> CurrentUser:
|
||||
admin_roles = {"site_admin", "tenant_admin"}
|
||||
if not admin_roles.intersection(user.roles):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要管理员权限(site_admin 或 tenant_admin)",
|
||||
)
|
||||
return user
|
||||
|
||||
return _dependency
|
||||
|
||||
|
||||
# ── GET /api/admin/applications ───────────────────────────
|
||||
|
||||
|
||||
@router.get("/applications", response_model=list[ApplicationResponse])
|
||||
async def list_applications(
|
||||
status_filter: Optional[str] = Query(
|
||||
None, alias="status", description="按状态过滤:pending / approved / rejected"
|
||||
),
|
||||
user: CurrentUser = Depends(_require_admin()),
|
||||
):
|
||||
"""查询申请列表,可按 status 过滤。"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
if status_filter:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, site_code, applied_role_text, status,
|
||||
review_note, created_at::text, reviewed_at::text
|
||||
FROM auth.user_applications
|
||||
WHERE status = %s
|
||||
ORDER BY created_at DESC
|
||||
""",
|
||||
(status_filter,),
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, site_code, applied_role_text, status,
|
||||
review_note, created_at::text, reviewed_at::text
|
||||
FROM auth.user_applications
|
||||
ORDER BY created_at DESC
|
||||
"""
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
return [
|
||||
ApplicationResponse(
|
||||
id=r[0],
|
||||
site_code=r[1],
|
||||
applied_role_text=r[2],
|
||||
status=r[3],
|
||||
review_note=r[4],
|
||||
created_at=r[5],
|
||||
reviewed_at=r[6],
|
||||
)
|
||||
for r in rows
|
||||
]
|
||||
|
||||
|
||||
# ── GET /api/admin/applications/{id} ─────────────────────
|
||||
|
||||
|
||||
@router.get("/applications/{application_id}")
|
||||
async def get_application_detail(
|
||||
application_id: int,
|
||||
user: CurrentUser = Depends(_require_admin()),
|
||||
):
|
||||
"""查询申请详情 + 候选匹配。"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, user_id, site_code, site_id, applied_role_text,
|
||||
phone, employee_number, status,
|
||||
review_note, created_at::text, reviewed_at::text
|
||||
FROM auth.user_applications
|
||||
WHERE id = %s
|
||||
""",
|
||||
(application_id,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if row is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="申请不存在",
|
||||
)
|
||||
|
||||
app_data = {
|
||||
"id": row[0],
|
||||
"user_id": row[1],
|
||||
"site_code": row[2],
|
||||
"site_id": row[3],
|
||||
"applied_role_text": row[4],
|
||||
"phone": row[5],
|
||||
"employee_number": row[6],
|
||||
"status": row[7],
|
||||
"review_note": row[8],
|
||||
"created_at": row[9],
|
||||
"reviewed_at": row[10],
|
||||
}
|
||||
|
||||
# 查找候选匹配
|
||||
candidates_raw = await find_candidates(
|
||||
site_id=app_data["site_id"],
|
||||
phone=app_data["phone"],
|
||||
employee_number=app_data["employee_number"],
|
||||
)
|
||||
candidates = [MatchCandidate(**c) for c in candidates_raw]
|
||||
|
||||
return {
|
||||
"application": app_data,
|
||||
"candidates": [c.model_dump() for c in candidates],
|
||||
}
|
||||
|
||||
|
||||
# ── POST /api/admin/applications/{id}/approve ────────────
|
||||
|
||||
|
||||
@router.post("/applications/{application_id}/approve", response_model=ApplicationResponse)
|
||||
async def approve(
|
||||
application_id: int,
|
||||
body: ApproveRequest,
|
||||
user: CurrentUser = Depends(_require_admin()),
|
||||
):
|
||||
"""批准申请:分配角色 + 可选绑定。"""
|
||||
result = await approve_application(
|
||||
application_id=application_id,
|
||||
reviewer_id=user.user_id,
|
||||
role_id=body.role_id,
|
||||
binding=body.binding,
|
||||
review_note=body.review_note,
|
||||
)
|
||||
return ApplicationResponse(**result)
|
||||
|
||||
|
||||
# ── POST /api/admin/applications/{id}/reject ─────────────
|
||||
|
||||
|
||||
@router.post("/applications/{application_id}/reject", response_model=ApplicationResponse)
|
||||
async def reject(
|
||||
application_id: int,
|
||||
body: RejectRequest,
|
||||
user: CurrentUser = Depends(_require_admin()),
|
||||
):
|
||||
"""拒绝申请:记录拒绝原因。"""
|
||||
result = await reject_application(
|
||||
application_id=application_id,
|
||||
reviewer_id=user.user_id,
|
||||
review_note=body.review_note,
|
||||
)
|
||||
return ApplicationResponse(**result)
|
||||
Reference in New Issue
Block a user