2185 lines
74 KiB
Python
2185 lines
74 KiB
Python
"""运营端管理接口(租户、角色下拉等)"""
|
||
import os
|
||
import uuid
|
||
from datetime import datetime
|
||
from typing import Any, List, Optional
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, UploadFile, File, Form
|
||
from pydantic import BaseModel, ConfigDict, Field
|
||
from sqlalchemy import and_, or_
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.config import settings
|
||
from app.core.data_scope import apply_data_scope, assert_row_in_data_scope
|
||
from app.core.database import get_db
|
||
from app.core.menu_guard import require_admin_or_super_admin, require_menu_code
|
||
from app.core.response import success_response, error_response
|
||
from app.core.attachment_biz import resolve_upload_folder
|
||
from app.core.storage import storage
|
||
from app.crud import tenant as tenant_svc
|
||
from app.crud import dict_type as dict_type_svc
|
||
from app.crud import dict_data as dict_data_svc
|
||
from app.services import dict_ensure as dict_ensure_svc
|
||
from app.crud import dept as dept_svc
|
||
from app.crud import site as site_svc
|
||
from app.crud import post as post_svc
|
||
from app.crud import menu as menu_svc
|
||
from app.crud import role as role_svc
|
||
from app.crud import user as user_svc
|
||
from app.crud import role as role_svc
|
||
from app.crud import user as user_svc
|
||
from app.crud import oss as oss_svc
|
||
from app.models.models import SysDept, SysDictData, SysRole, SysUser, SysUserRole
|
||
from app.schemas.menu_favorite import (
|
||
MenuFavoriteCreateBody,
|
||
MenuFavoriteDeleteBody,
|
||
MenuFavoriteReorderBody,
|
||
)
|
||
from app.services.web_auth_roles import _map_role_name_to_web
|
||
from app.utils.excel_export import (
|
||
EXPORT_MAX_ROWS,
|
||
ExportColumn,
|
||
ExportValidationError,
|
||
export_rows_to_xlsx_response,
|
||
filter_export_columns,
|
||
)
|
||
|
||
router = APIRouter(prefix="/web/admin", tags=["运营端管理"])
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 角色下拉(用户弹窗等)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@router.get("/roles")
|
||
def list_roles_for_select(
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""返回运营端角色下拉选项(label=库中角色名,value=Web 角色码)。"""
|
||
rows: List[SysRole] = (
|
||
db.query(SysRole)
|
||
.filter(SysRole.del_flag == 0, SysRole.status == "1")
|
||
.order_by(SysRole.create_time.desc())
|
||
.all()
|
||
)
|
||
seen: set[str] = set()
|
||
out: List[dict] = []
|
||
for r in rows:
|
||
name = (r.role_name or "").strip()
|
||
if not name:
|
||
continue
|
||
code = _map_role_name_to_web(name)
|
||
key = f"{code}:{name}"
|
||
if key in seen:
|
||
continue
|
||
seen.add(key)
|
||
out.append({"label": name, "value": code})
|
||
if not out:
|
||
out = [
|
||
{"label": "超级管理员", "value": "super_admin"},
|
||
{"label": "运营管理员", "value": "admin"},
|
||
]
|
||
return success_response(out)
|
||
|
||
|
||
@router.get("/users/options")
|
||
def list_users_for_select(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""返回用户下拉选项(label=昵称,value=用户ID,附带 nickName/userNo)。
|
||
|
||
供申请人/审批人等选择场景使用,返回本租户全部有效用户,
|
||
不做业务数据范围过滤(否则「仅本人」角色会按 create_by 滤空,连自己都选不到)。
|
||
"""
|
||
tenant_no = _get_tenant_no(db, user)
|
||
q = db.query(SysUser).filter(SysUser.del_flag == 0, SysUser.status == "1")
|
||
q = q.filter(SysUser.tenant_no == tenant_no)
|
||
rows = q.order_by(SysUser.create_time.desc()).all()
|
||
out = [
|
||
{
|
||
"label": (r.nick_name or r.user_no or ""),
|
||
"value": r.id,
|
||
"nickName": r.nick_name or "",
|
||
"userNo": r.user_no or "",
|
||
}
|
||
for r in rows
|
||
]
|
||
return success_response(out)
|
||
|
||
|
||
@router.get("/roles/options")
|
||
def list_roles_for_designer(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""返回角色下拉选项(label=角色名,value=角色ID UUID),供流程设计器审批人选择使用。
|
||
与 /roles 不同,此端点返回角色 UUID 作为 value(引擎解析审批人需要 UUID)。
|
||
角色为租户级主数据选项,不做 create_by 数据范围过滤。
|
||
"""
|
||
tenant_no = _get_tenant_no(db, user)
|
||
q = db.query(SysRole).filter(SysRole.del_flag == 0, SysRole.status == "1")
|
||
q = q.filter(SysRole.tenant_no == tenant_no)
|
||
rows = q.order_by(SysRole.create_time.desc()).all()
|
||
out = [{"label": (r.role_name or "").strip(), "value": r.id} for r in rows if (r.role_name or "").strip()]
|
||
return success_response(out)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 租户管理
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TenantCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
tenant_no: str = Field(..., alias="tenantNo")
|
||
tenant_name: str = Field(..., alias="tenantName")
|
||
contact_name: Optional[str] = Field(None, alias="contactName")
|
||
contact_phone: Optional[str] = Field(None, alias="contactPhone")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class TenantUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
tenant_no: Optional[str] = Field(None, alias="tenantNo")
|
||
tenant_name: Optional[str] = Field(None, alias="tenantName")
|
||
contact_name: Optional[str] = Field(None, alias="contactName")
|
||
contact_phone: Optional[str] = Field(None, alias="contactPhone")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/tenants")
|
||
def list_tenants(
|
||
request: Request,
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=500, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
items, total = tenant_svc.list_tenants(
|
||
db,
|
||
page=page,
|
||
page_size=pageSize,
|
||
keyword=keyword,
|
||
tenant_no_like=request.query_params.get("tenantNo"),
|
||
tenant_name=request.query_params.get("tenantName"),
|
||
contact_name=request.query_params.get("contactName"),
|
||
contact_phone=request.query_params.get("contactPhone"),
|
||
user_info=_user,
|
||
)
|
||
total_pages = tenant_svc.total_pages(total, pageSize)
|
||
return success_response(
|
||
{
|
||
"list": items,
|
||
"total": total,
|
||
"page": page,
|
||
"pageSize": pageSize,
|
||
"totalPages": total_pages,
|
||
}
|
||
)
|
||
|
||
|
||
|
||
TENANT_EXPORT_COLUMNS = [
|
||
ExportColumn(title="租户编号", field="tenantNo"),
|
||
ExportColumn(title="租户名称", field="tenantName"),
|
||
ExportColumn(title="联系人", field="contactName"),
|
||
ExportColumn(title="联系电话", field="contactPhone"),
|
||
ExportColumn(title="创建时间", field="createTime"),
|
||
]
|
||
|
||
|
||
class TenantExportBody(BaseModel):
|
||
keyword: Optional[str] = None
|
||
ids: Optional[List[str]] = None
|
||
columns: Optional[List[str]] = None
|
||
|
||
|
||
@router.post("/tenants/export")
|
||
def export_tenants(
|
||
body: TenantExportBody,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""导出租户列表(当前筛选全量,或 ids 已选)。"""
|
||
try:
|
||
items, _ = tenant_svc.list_tenants(
|
||
db,
|
||
page=1,
|
||
page_size=EXPORT_MAX_ROWS,
|
||
keyword=body.keyword,
|
||
ids=body.ids,
|
||
max_page_size=EXPORT_MAX_ROWS,
|
||
user_info=_user,
|
||
)
|
||
cols = filter_export_columns(TENANT_EXPORT_COLUMNS, body.columns)
|
||
date_str = datetime.now().strftime("%Y%m%d")
|
||
return export_rows_to_xlsx_response(
|
||
db,
|
||
tenant_no=None,
|
||
sheet_title="租户列表",
|
||
columns=cols,
|
||
rows=items,
|
||
filename_stem=f"租户列表_{date_str}",
|
||
)
|
||
except ExportValidationError as e:
|
||
return error_response(400, str(e))
|
||
except Exception as e:
|
||
return error_response(500, str(e))
|
||
|
||
|
||
@router.post("/tenants")
|
||
def create_tenant(
|
||
body: TenantCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
obj = tenant_svc.create_tenant(
|
||
db,
|
||
tenant_no=body.tenant_no,
|
||
tenant_name=body.tenant_name,
|
||
contact_name=body.contact_name,
|
||
contact_phone=body.contact_phone,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(tenant_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/tenants/{tenant_id}")
|
||
def update_tenant(
|
||
tenant_id: str,
|
||
body: TenantUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = tenant_svc.update_tenant(
|
||
db,
|
||
tenant_id,
|
||
tenant_no=payload.get("tenant_no"),
|
||
tenant_name=payload.get("tenant_name"),
|
||
contact_name=payload.get("contact_name"),
|
||
contact_phone=payload.get("contact_phone"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(tenant_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/tenants/{tenant_id}")
|
||
def delete_tenant(
|
||
tenant_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_svc.soft_delete_tenant(db, tenant_id, user_info=_user)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Helper: resolve tenant_no from user context
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def _get_tenant_no(db: Session, user: dict) -> str:
|
||
user_id = user.get("user_id")
|
||
if not user_id:
|
||
return "shanxia2.0"
|
||
sys_user = db.query(SysUser).filter(SysUser.id == user_id).first()
|
||
return (sys_user.tenant_no if sys_user else None) or "shanxia2.0"
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 用户管理
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class UserCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
user_no: str = Field(..., alias="userNo")
|
||
nick_name: str = Field(..., alias="nickName")
|
||
password: str = Field(..., alias="password")
|
||
dept_id: Optional[str] = Field(None, alias="deptId")
|
||
email: Optional[str] = Field("", alias="email")
|
||
phone_no: Optional[str] = Field("", alias="phoneNo")
|
||
sex: Optional[str] = Field("0", alias="sex")
|
||
avatar: Optional[str] = Field(None, alias="avatar")
|
||
status: Optional[str] = Field("1", alias="status")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
role_ids: Optional[List[str]] = Field(None, alias="roleIds")
|
||
post_ids: Optional[List[str]] = Field(None, alias="postIds")
|
||
|
||
|
||
class UserUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
nick_name: Optional[str] = Field(None, alias="nickName")
|
||
dept_id: Optional[str] = Field(None, alias="deptId")
|
||
email: Optional[str] = Field(None, alias="email")
|
||
phone_no: Optional[str] = Field(None, alias="phoneNo")
|
||
sex: Optional[str] = Field(None, alias="sex")
|
||
avatar: Optional[str] = Field(None, alias="avatar")
|
||
status: Optional[str] = Field(None, alias="status")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
role_ids: Optional[List[str]] = Field(None, alias="roleIds")
|
||
post_ids: Optional[List[str]] = Field(None, alias="postIds")
|
||
|
||
|
||
USER_EXPORT_COLUMNS = [
|
||
ExportColumn(title="用户账号", field="userNo"),
|
||
ExportColumn(title="用户名", field="nickName"),
|
||
ExportColumn(title="所属组织", field="deptName"),
|
||
ExportColumn(title="角色", field="roles"),
|
||
ExportColumn(title="手机号", field="phoneNo"),
|
||
ExportColumn(title="状态", field="status", dict_type="sys_status"),
|
||
ExportColumn(title="创建时间", field="createTime"),
|
||
]
|
||
|
||
|
||
class UserExportBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
keyword: Optional[str] = None
|
||
dept_id: Optional[str] = Field(None, alias="deptId")
|
||
status: Optional[str] = None
|
||
ids: Optional[List[str]] = None
|
||
columns: Optional[List[str]] = None
|
||
|
||
|
||
@router.get("/users")
|
||
def list_users(
|
||
request: Request,
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=500, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
deptId: Optional[str] = Query(None, alias="deptId"),
|
||
status: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("system:user:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
query = db.query(SysUser).filter(
|
||
SysUser.del_flag == 0,
|
||
SysUser.tenant_no == tenant_no,
|
||
)
|
||
kw = (keyword or "").strip()
|
||
if kw:
|
||
like = f"%{kw}%"
|
||
query = query.filter(
|
||
or_(
|
||
SysUser.user_no.like(like),
|
||
SysUser.nick_name.like(like),
|
||
SysUser.phone_no.like(like),
|
||
)
|
||
)
|
||
user_no = (request.query_params.get("userNo") or "").strip()
|
||
if user_no:
|
||
query = query.filter(SysUser.user_no.like(f"%{user_no}%"))
|
||
nick_name = (request.query_params.get("nickName") or "").strip()
|
||
if nick_name:
|
||
query = query.filter(SysUser.nick_name.like(f"%{nick_name}%"))
|
||
phone_no = (request.query_params.get("phoneNo") or "").strip()
|
||
if phone_no:
|
||
query = query.filter(SysUser.phone_no.like(f"%{phone_no}%"))
|
||
if deptId:
|
||
query = query.filter(SysUser.dept_id == deptId)
|
||
status_in = (request.query_params.get("statusIn") or "").strip()
|
||
status_values = [s.strip() for s in status_in.split(",") if s.strip()]
|
||
if not status_values and status:
|
||
status_values = [status]
|
||
if status_values:
|
||
query = query.filter(SysUser.status.in_(status_values))
|
||
query = apply_data_scope(query, SysUser, user, db, tenant_no)
|
||
total = query.count()
|
||
items = query.order_by(SysUser.create_time.desc()).offset((page - 1) * pageSize).limit(pageSize).all()
|
||
dept_ids = list({r.dept_id for r in items if r.dept_id})
|
||
dept_map: dict[str, str] = {}
|
||
if dept_ids:
|
||
for d in db.query(SysDept).filter(SysDept.id.in_(dept_ids), SysDept.del_flag == 0).all():
|
||
dept_map[d.id] = d.dept_name or ""
|
||
result_list = [
|
||
user_svc._row_to_dict(r, dept_name=dept_map.get(r.dept_id or "", "")) for r in items
|
||
]
|
||
user_ids = [d["id"] for d in result_list if d.get("id")]
|
||
role_map: dict[str, list[str]] = {uid: [] for uid in user_ids}
|
||
if user_ids:
|
||
for ur in (
|
||
db.query(SysUserRole)
|
||
.filter(
|
||
SysUserRole.user_id.in_(user_ids),
|
||
SysUserRole.del_flag == 0,
|
||
)
|
||
.all()
|
||
):
|
||
role_map.setdefault(ur.user_id, []).append(ur.role_id)
|
||
for d in result_list:
|
||
d["roleIds"] = role_map.get(d["id"], [])
|
||
tp = user_svc.total_pages(total, pageSize)
|
||
return success_response({"list": result_list, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.post("/users/export")
|
||
def export_users(
|
||
body: UserExportBody,
|
||
user: dict = Depends(require_menu_code("system:user:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""导出用户列表(当前筛选全量,或 ids 已选)。"""
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
query = db.query(SysUser).filter(
|
||
SysUser.del_flag == 0,
|
||
SysUser.tenant_no == tenant_no,
|
||
)
|
||
kw = (body.keyword or "").strip()
|
||
if kw:
|
||
like = f"%{kw}%"
|
||
query = query.filter(
|
||
or_(
|
||
SysUser.user_no.like(like),
|
||
SysUser.nick_name.like(like),
|
||
SysUser.phone_no.like(like),
|
||
)
|
||
)
|
||
if body.dept_id:
|
||
query = query.filter(SysUser.dept_id == body.dept_id)
|
||
if body.status:
|
||
query = query.filter(SysUser.status == body.status)
|
||
if body.ids:
|
||
query = query.filter(SysUser.id.in_(body.ids))
|
||
query = apply_data_scope(query, SysUser, user, db, tenant_no)
|
||
rows_orm = query.order_by(SysUser.create_time.desc()).all()
|
||
dept_ids = list({r.dept_id for r in rows_orm if r.dept_id})
|
||
dept_map: dict[str, str] = {}
|
||
if dept_ids:
|
||
for d in db.query(SysDept).filter(SysDept.id.in_(dept_ids), SysDept.del_flag == 0).all():
|
||
dept_map[d.id] = d.dept_name or ""
|
||
result_list = [
|
||
user_svc._row_to_dict(r, dept_name=dept_map.get(r.dept_id or "", "")) for r in rows_orm
|
||
]
|
||
user_ids = [d["id"] for d in result_list if d.get("id")]
|
||
role_map: dict[str, list[str]] = {uid: [] for uid in user_ids}
|
||
role_name_by_id: dict[str, str] = {}
|
||
if user_ids:
|
||
for ur in (
|
||
db.query(SysUserRole)
|
||
.filter(
|
||
SysUserRole.user_id.in_(user_ids),
|
||
SysUserRole.del_flag == 0,
|
||
)
|
||
.all()
|
||
):
|
||
role_map.setdefault(ur.user_id, []).append(ur.role_id)
|
||
role_ids = list({rid for ids in role_map.values() for rid in ids})
|
||
if role_ids:
|
||
for role in db.query(SysRole).filter(SysRole.id.in_(role_ids), SysRole.del_flag == 0).all():
|
||
role_name_by_id[role.id] = role.role_name or role.id
|
||
for d in result_list:
|
||
names = [role_name_by_id.get(rid, rid) for rid in role_map.get(d["id"], [])]
|
||
d["roles"] = "、".join(names)
|
||
cols = filter_export_columns(USER_EXPORT_COLUMNS, body.columns)
|
||
date_str = datetime.now().strftime("%Y%m%d")
|
||
return export_rows_to_xlsx_response(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
sheet_title="用户列表",
|
||
columns=cols,
|
||
rows=result_list,
|
||
filename_stem=f"用户列表_{date_str}",
|
||
)
|
||
except ExportValidationError as e:
|
||
return error_response(400, str(e))
|
||
except Exception as e:
|
||
return error_response(500, str(e))
|
||
|
||
|
||
@router.get("/users/{user_id}")
|
||
def get_user_by_id(
|
||
user_id: str,
|
||
user: dict = Depends(require_menu_code("system:user:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
row = user_svc.get_user(db, user_id)
|
||
if not row:
|
||
return error_response(404, "用户不存在")
|
||
if row.tenant_no != tenant_no and user.get("role") != "super_admin":
|
||
return error_response(403, "无权查看该用户")
|
||
try:
|
||
assert_row_in_data_scope(db, row, SysUser, user, tenant_no)
|
||
except HTTPException:
|
||
return error_response(403, "无权查看该用户")
|
||
data = user_svc.get_user_with_relations(db, user_id)
|
||
return success_response(data)
|
||
|
||
|
||
@router.post("/users")
|
||
def create_user(
|
||
body: UserCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
if user_svc.user_no_exists(db, body.user_no, tenant_no):
|
||
return error_response(400, "用户账号已存在")
|
||
obj = user_svc.create_user(
|
||
db,
|
||
user_no=body.user_no,
|
||
nick_name=body.nick_name,
|
||
password=body.password,
|
||
dept_id=body.dept_id,
|
||
email=body.email,
|
||
phone_no=body.phone_no,
|
||
sex=body.sex,
|
||
avatar=body.avatar,
|
||
status=body.status,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
role_ids=body.role_ids,
|
||
post_ids=body.post_ids,
|
||
)
|
||
return success_response(user_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/users/{user_id}")
|
||
def update_user(
|
||
user_id: str,
|
||
body: UserUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = user_svc.update_user(
|
||
db,
|
||
user_id,
|
||
nick_name=payload.get("nick_name"),
|
||
dept_id=payload.get("dept_id"),
|
||
email=payload.get("email"),
|
||
phone_no=payload.get("phone_no"),
|
||
sex=payload.get("sex"),
|
||
avatar=payload.get("avatar"),
|
||
status=payload.get("status"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id_updater=user.get("user_id"),
|
||
role_ids=payload.get("role_ids"),
|
||
post_ids=payload.get("post_ids"),
|
||
)
|
||
return success_response(user_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/users/{user_id}")
|
||
def delete_user(
|
||
user_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
user_svc.soft_delete_user(db, user_id, user_info=_user)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/users/{user_id}/reset-password")
|
||
def reset_user_password(
|
||
user_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
user_svc.reset_password(db, user_id, "111111")
|
||
return success_response(None, message="密码已重置为 111111")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 字典类型管理
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class DictTypeCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
dict_name: str = Field(..., alias="dictName")
|
||
dict_type: str = Field(..., alias="dictType")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class DictTypeUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
dict_name: Optional[str] = Field(None, alias="dictName")
|
||
dict_type: Optional[str] = Field(None, alias="dictType")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/dict-types")
|
||
def list_dict_types(
|
||
request: Request,
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=500, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("system:dict:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = dict_type_svc.list_dict_types(
|
||
db,
|
||
page=page,
|
||
page_size=pageSize,
|
||
keyword=keyword,
|
||
dict_name=request.query_params.get("dictName"),
|
||
dict_type=request.query_params.get("dictType"),
|
||
tenant_no=tenant_no,
|
||
user_info=user,
|
||
)
|
||
tp = dict_type_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
|
||
DICT_TYPE_EXPORT_COLUMNS = [
|
||
ExportColumn(title="字典名称", field="dictName"),
|
||
ExportColumn(title="字典类型编码", field="dictType"),
|
||
ExportColumn(title="备注", field="remark"),
|
||
ExportColumn(title="创建时间", field="createTime"),
|
||
]
|
||
|
||
|
||
class DictTypeExportBody(BaseModel):
|
||
keyword: Optional[str] = None
|
||
ids: Optional[List[str]] = None
|
||
columns: Optional[List[str]] = None
|
||
|
||
|
||
@router.post("/dict-types/export")
|
||
def export_dict_types(
|
||
body: DictTypeExportBody,
|
||
user: dict = Depends(require_menu_code("system:dict:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""导出字典类型列表(当前筛选全量,或 ids 已选)。"""
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, _ = dict_type_svc.list_dict_types(
|
||
db,
|
||
page=1,
|
||
page_size=EXPORT_MAX_ROWS,
|
||
keyword=body.keyword,
|
||
tenant_no=tenant_no,
|
||
ids=body.ids,
|
||
max_page_size=EXPORT_MAX_ROWS,
|
||
user_info=user,
|
||
)
|
||
cols = filter_export_columns(DICT_TYPE_EXPORT_COLUMNS, body.columns)
|
||
date_str = datetime.now().strftime("%Y%m%d")
|
||
return export_rows_to_xlsx_response(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
sheet_title="字典类型",
|
||
columns=cols,
|
||
rows=items,
|
||
filename_stem=f"字典类型_{date_str}",
|
||
)
|
||
except ExportValidationError as e:
|
||
return error_response(400, str(e))
|
||
except Exception as e:
|
||
return error_response(500, str(e))
|
||
|
||
|
||
@router.post("/dict-types")
|
||
def create_dict_type(
|
||
body: DictTypeCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = dict_type_svc.create_dict_type(
|
||
db,
|
||
dict_name=body.dict_name,
|
||
dict_type=body.dict_type,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(dict_type_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/dict-types/{dict_type_id}")
|
||
def update_dict_type(
|
||
dict_type_id: str,
|
||
body: DictTypeUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = dict_type_svc.update_dict_type(
|
||
db,
|
||
dict_type_id,
|
||
dict_name=payload.get("dict_name"),
|
||
dict_type=payload.get("dict_type"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
tenant_no=tenant_no,
|
||
)
|
||
return success_response(dict_type_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/dict-types/{dict_type_id}")
|
||
def delete_dict_type(
|
||
dict_type_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
dict_type_svc.soft_delete_dict_type(db, dict_type_id, user_info=_user)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 字典数据管理
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class DictDataCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
dict_label: str = Field(..., alias="dictLabel")
|
||
dict_value: str = Field(..., alias="dictValue")
|
||
dict_type: str = Field(..., alias="dictType")
|
||
tag_type: Optional[str] = Field("", alias="tagType")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class DictDataUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
dict_label: Optional[str] = Field(None, alias="dictLabel")
|
||
dict_value: Optional[str] = Field(None, alias="dictValue")
|
||
tag_type: Optional[str] = Field(None, alias="tagType")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/dict-data")
|
||
def list_dict_data(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=500, alias="pageSize"),
|
||
dictType: Optional[str] = None,
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("system:dict:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = dict_data_svc.list_dict_data(
|
||
db, page=page, page_size=pageSize, dict_type_code=dictType, keyword=keyword, tenant_no=tenant_no, user_info=user
|
||
)
|
||
tp = dict_data_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
@router.post("/dict-data")
|
||
def create_dict_data(
|
||
body: DictDataCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = dict_data_svc.create_dict_data(
|
||
db,
|
||
dict_label=body.dict_label,
|
||
dict_value=body.dict_value,
|
||
dict_type=body.dict_type,
|
||
tag_type=body.tag_type,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(dict_data_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/dict-data/{data_id}")
|
||
def update_dict_data(
|
||
data_id: str,
|
||
body: DictDataUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = dict_data_svc.update_dict_data(
|
||
db,
|
||
data_id,
|
||
dict_label=payload.get("dict_label"),
|
||
dict_value=payload.get("dict_value"),
|
||
tag_type=payload.get("tag_type"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(dict_data_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/dict-data/{data_id}")
|
||
def delete_dict_data(
|
||
data_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
dict_data_svc.soft_delete_dict_data(db, data_id, user_info=_user)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.get("/dict-data/options")
|
||
def dict_data_options(
|
||
dictType: str = Query(..., alias="dictType"),
|
||
user: dict = Depends(require_menu_code("system:dict:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
# 字典选项为租户主数据,表单共用;不做数据范围过滤
|
||
items = dict_data_svc.list_dict_data_options(
|
||
db, dict_type_code=dictType, tenant_no=tenant_no, user_info=None
|
||
)
|
||
return success_response(items)
|
||
|
||
|
||
@router.get("/dict-data/all")
|
||
def dict_data_all(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
q = db.query(SysDictData).filter(
|
||
and_(SysDictData.tenant_no == tenant_no, SysDictData.del_flag == 0)
|
||
)
|
||
rows = q.order_by(SysDictData.order_num.asc()).all()
|
||
grouped = {}
|
||
for r in rows:
|
||
key = r.dict_type
|
||
if key not in grouped:
|
||
grouped[key] = []
|
||
grouped[key].append({
|
||
"label": r.dict_label,
|
||
"value": r.dict_value,
|
||
"tagType": r.tag_type or ""
|
||
})
|
||
return success_response(grouped)
|
||
|
||
|
||
class DictEnsureBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
dict_type: str = Field(..., alias="dictType")
|
||
label: str
|
||
preferred_value: Optional[str] = Field(None, alias="preferredValue")
|
||
|
||
|
||
class DictEnsureRemoveBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
dict_type: str = Field(..., alias="dictType")
|
||
value: str
|
||
|
||
|
||
@router.post("/dict-data/ensure")
|
||
def ensure_dict_data_api(
|
||
body: DictEnsureBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
data = dict_ensure_svc.ensure_dict_data(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
dict_type=body.dict_type,
|
||
label=body.label,
|
||
user_id=user.get("user_id") or user.get("id") or "",
|
||
preferred_value=body.preferred_value,
|
||
)
|
||
db.commit()
|
||
return success_response(data, message="已保存")
|
||
except dict_ensure_svc.DictEnsureError as e:
|
||
db.rollback()
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.post("/dict-data/ensure-remove")
|
||
def ensure_remove_dict_data_api(
|
||
body: DictEnsureRemoveBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
dict_ensure_svc.ensure_remove_dict_data(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
dict_type=body.dict_type,
|
||
value=body.value,
|
||
user_id=user.get("user_id") or user.get("id") or "",
|
||
)
|
||
db.commit()
|
||
return success_response(None, message="已删除")
|
||
except dict_ensure_svc.DictEnsureError as e:
|
||
db.rollback()
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 组织管理
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class DeptCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
parent_id: str = Field("0", alias="parentId")
|
||
dept_name: str = Field(..., alias="deptName")
|
||
dept_category_code: Optional[str] = Field(None, alias="deptCategoryCode")
|
||
status: Optional[str] = Field("1")
|
||
leader: Optional[str] = None
|
||
leader_phone: Optional[str] = Field(None, alias="leaderPhone")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class DeptUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
dept_name: Optional[str] = Field(None, alias="deptName")
|
||
dept_category_code: Optional[str] = Field(None, alias="deptCategoryCode")
|
||
status: Optional[str] = None
|
||
leader: Optional[str] = None
|
||
leader_phone: Optional[str] = Field(None, alias="leaderPhone")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/depts")
|
||
def list_depts(
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("system:dept:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
tree = dept_svc.list_depts_tree(db, keyword=keyword, tenant_no=tenant_no, user_info=user)
|
||
return success_response(tree)
|
||
|
||
|
||
|
||
DEPT_EXPORT_COLUMNS = [
|
||
ExportColumn(title="组织名称", field="deptName"),
|
||
ExportColumn(title="组织类别", field="deptCategoryCode"),
|
||
ExportColumn(title="状态", field="status", dict_type="sys_status"),
|
||
ExportColumn(title="负责人", field="leader"),
|
||
ExportColumn(title="联系电话", field="leaderPhone"),
|
||
ExportColumn(title="创建时间", field="createTime"),
|
||
]
|
||
|
||
|
||
class DeptExportBody(BaseModel):
|
||
keyword: Optional[str] = None
|
||
ids: Optional[List[str]] = None
|
||
columns: Optional[List[str]] = None
|
||
|
||
|
||
@router.post("/depts/export")
|
||
def export_depts(
|
||
body: DeptExportBody,
|
||
user: dict = Depends(require_menu_code("system:dept:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""导出组织列表(扁平行;当前筛选或已选)。"""
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
rows = dept_svc.list_depts(db, keyword=body.keyword, tenant_no=tenant_no, user_info=user)
|
||
if body.ids:
|
||
idset = set(body.ids)
|
||
rows = [r for r in rows if r.get("id") in idset]
|
||
cols = filter_export_columns(DEPT_EXPORT_COLUMNS, body.columns)
|
||
date_str = datetime.now().strftime("%Y%m%d")
|
||
return export_rows_to_xlsx_response(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
sheet_title="组织列表",
|
||
columns=cols,
|
||
rows=rows,
|
||
filename_stem=f"组织列表_{date_str}",
|
||
)
|
||
except ExportValidationError as e:
|
||
return error_response(400, str(e))
|
||
except Exception as e:
|
||
return error_response(500, str(e))
|
||
|
||
|
||
@router.post("/depts")
|
||
def create_dept(
|
||
body: DeptCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = dept_svc.create_dept(
|
||
db,
|
||
parent_id=body.parent_id,
|
||
dept_name=body.dept_name,
|
||
dept_category_code=body.dept_category_code,
|
||
status=body.status,
|
||
leader=body.leader,
|
||
leader_phone=body.leader_phone,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(dept_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/depts/{dept_id}")
|
||
def update_dept(
|
||
dept_id: str,
|
||
body: DeptUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = dept_svc.update_dept(
|
||
db,
|
||
dept_id,
|
||
dept_name=payload.get("dept_name"),
|
||
dept_category_code=payload.get("dept_category_code"),
|
||
status=payload.get("status"),
|
||
leader=payload.get("leader"),
|
||
leader_phone=payload.get("leader_phone"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(dept_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/depts/{dept_id}")
|
||
def delete_dept(
|
||
dept_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
dept_svc.soft_delete_dept(db, dept_id, user_info=_user)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 站点管理
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class SiteCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
site_name: str = Field(..., alias="siteName")
|
||
site_code: str = Field(..., alias="siteCode")
|
||
site_type: Optional[str] = Field(None, alias="siteType")
|
||
status: Optional[str] = Field("1")
|
||
address: Optional[str] = None
|
||
contact_name: Optional[str] = Field(None, alias="contactName")
|
||
contact_phone: Optional[str] = Field(None, alias="contactPhone")
|
||
business_scope: Optional[str] = Field(None, alias="businessScope")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class SiteUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
site_name: Optional[str] = Field(None, alias="siteName")
|
||
site_code: Optional[str] = Field(None, alias="siteCode")
|
||
site_type: Optional[str] = Field(None, alias="siteType")
|
||
status: Optional[str] = None
|
||
address: Optional[str] = None
|
||
contact_name: Optional[str] = Field(None, alias="contactName")
|
||
contact_phone: Optional[str] = Field(None, alias="contactPhone")
|
||
business_scope: Optional[str] = Field(None, alias="businessScope")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/sites")
|
||
def list_sites(
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=500, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("system:site:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = site_svc.list_sites(db, page=page, page_size=pageSize, keyword=keyword, tenant_no=tenant_no, user_info=user)
|
||
tp = site_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
|
||
SITE_EXPORT_COLUMNS = [
|
||
ExportColumn(title="站点名称", field="siteName"),
|
||
ExportColumn(title="站点编码", field="siteCode"),
|
||
ExportColumn(title="站点类型", field="siteType"),
|
||
ExportColumn(title="状态", field="status", dict_type="sys_status"),
|
||
ExportColumn(title="地址", field="address"),
|
||
ExportColumn(title="联系人", field="contactName"),
|
||
ExportColumn(title="联系电话", field="contactPhone"),
|
||
ExportColumn(title="创建时间", field="createTime"),
|
||
]
|
||
|
||
|
||
class SiteExportBody(BaseModel):
|
||
keyword: Optional[str] = None
|
||
ids: Optional[List[str]] = None
|
||
columns: Optional[List[str]] = None
|
||
|
||
|
||
@router.post("/sites/export")
|
||
def export_sites(
|
||
body: SiteExportBody,
|
||
user: dict = Depends(require_menu_code("system:site:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""导出站点列表(当前筛选全量,或 ids 已选)。"""
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, _ = site_svc.list_sites(
|
||
db,
|
||
page=1,
|
||
page_size=EXPORT_MAX_ROWS,
|
||
keyword=body.keyword,
|
||
tenant_no=tenant_no,
|
||
ids=body.ids,
|
||
max_page_size=EXPORT_MAX_ROWS,
|
||
user_info=user,
|
||
)
|
||
cols = filter_export_columns(SITE_EXPORT_COLUMNS, body.columns)
|
||
date_str = datetime.now().strftime("%Y%m%d")
|
||
return export_rows_to_xlsx_response(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
sheet_title="站点列表",
|
||
columns=cols,
|
||
rows=items,
|
||
filename_stem=f"站点列表_{date_str}",
|
||
)
|
||
except ExportValidationError as e:
|
||
return error_response(400, str(e))
|
||
except Exception as e:
|
||
return error_response(500, str(e))
|
||
|
||
|
||
@router.post("/sites")
|
||
def create_site(
|
||
body: SiteCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = site_svc.create_site(
|
||
db,
|
||
site_name=body.site_name,
|
||
site_code=body.site_code,
|
||
site_type=body.site_type,
|
||
status=body.status,
|
||
address=body.address,
|
||
contact_name=body.contact_name,
|
||
contact_phone=body.contact_phone,
|
||
business_scope=body.business_scope,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(site_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/sites/{site_id}")
|
||
def update_site(
|
||
site_id: str,
|
||
body: SiteUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = site_svc.update_site(
|
||
db,
|
||
site_id,
|
||
site_name=payload.get("site_name"),
|
||
site_code=payload.get("site_code"),
|
||
site_type=payload.get("site_type"),
|
||
status=payload.get("status"),
|
||
address=payload.get("address"),
|
||
contact_name=payload.get("contact_name"),
|
||
contact_phone=payload.get("contact_phone"),
|
||
business_scope=payload.get("business_scope"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(site_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/sites/{site_id}")
|
||
def delete_site(
|
||
site_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
site_svc.soft_delete_site(db, site_id, user_info=_user)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 职位管理
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class PostCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
post_name: str = Field(..., alias="postName")
|
||
post_code: str = Field(..., alias="postCode")
|
||
status: Optional[str] = Field("1")
|
||
order_num: Optional[int] = Field(0, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class PostUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
post_name: Optional[str] = Field(None, alias="postName")
|
||
post_code: Optional[str] = Field(None, alias="postCode")
|
||
status: Optional[str] = None
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/posts")
|
||
def list_posts(
|
||
request: Request,
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=500, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("system:post:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = post_svc.list_posts(
|
||
db,
|
||
page=page,
|
||
page_size=pageSize,
|
||
keyword=keyword,
|
||
post_name=request.query_params.get("postName"),
|
||
post_code=request.query_params.get("postCode"),
|
||
status=request.query_params.get("status"),
|
||
status_in=request.query_params.get("statusIn"),
|
||
tenant_no=tenant_no,
|
||
user_info=user,
|
||
)
|
||
tp = post_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
|
||
POST_EXPORT_COLUMNS = [
|
||
ExportColumn(title="职位名称", field="postName"),
|
||
ExportColumn(title="职位编码", field="postCode"),
|
||
ExportColumn(title="状态", field="status", dict_type="sys_status"),
|
||
ExportColumn(title="创建时间", field="createTime"),
|
||
]
|
||
|
||
|
||
class PostExportBody(BaseModel):
|
||
keyword: Optional[str] = None
|
||
ids: Optional[List[str]] = None
|
||
columns: Optional[List[str]] = None
|
||
|
||
|
||
@router.post("/posts/export")
|
||
def export_posts(
|
||
body: PostExportBody,
|
||
user: dict = Depends(require_menu_code("system:post:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""导出职位列表(当前筛选全量,或 ids 已选)。"""
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, _ = post_svc.list_posts(
|
||
db,
|
||
page=1,
|
||
page_size=EXPORT_MAX_ROWS,
|
||
keyword=body.keyword,
|
||
tenant_no=tenant_no,
|
||
ids=body.ids,
|
||
max_page_size=EXPORT_MAX_ROWS,
|
||
user_info=user,
|
||
)
|
||
cols = filter_export_columns(POST_EXPORT_COLUMNS, body.columns)
|
||
date_str = datetime.now().strftime("%Y%m%d")
|
||
return export_rows_to_xlsx_response(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
sheet_title="职位列表",
|
||
columns=cols,
|
||
rows=items,
|
||
filename_stem=f"职位列表_{date_str}",
|
||
)
|
||
except ExportValidationError as e:
|
||
return error_response(400, str(e))
|
||
except Exception as e:
|
||
return error_response(500, str(e))
|
||
|
||
|
||
@router.post("/posts")
|
||
def create_post(
|
||
body: PostCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = post_svc.create_post(
|
||
db,
|
||
post_name=body.post_name,
|
||
post_code=body.post_code,
|
||
status=body.status,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(post_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/posts/{post_id}")
|
||
def update_post(
|
||
post_id: str,
|
||
body: PostUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = post_svc.update_post(
|
||
db,
|
||
post_id,
|
||
post_name=payload.get("post_name"),
|
||
post_code=payload.get("post_code"),
|
||
status=payload.get("status"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(post_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/posts/{post_id}")
|
||
def delete_post(
|
||
post_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
post_svc.soft_delete_post(db, post_id, user_info=_user)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
class PostUsersUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
user_ids: List[str] = Field(default_factory=list, alias="userIds")
|
||
|
||
|
||
@router.get("/posts/{post_id}/users")
|
||
def list_post_bound_users(
|
||
post_id: str,
|
||
user: dict = Depends(require_menu_code("system:post:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
post = post_svc.get_post(db, post_id)
|
||
if not post or post.tenant_no != tenant_no:
|
||
return error_response(404, "职位不存在")
|
||
ids = user_svc.list_user_ids_bound_to_post(db, post_id, tenant_no)
|
||
return success_response({"userIds": ids})
|
||
|
||
|
||
@router.put("/posts/{post_id}/users")
|
||
def update_post_bound_users(
|
||
post_id: str,
|
||
body: PostUsersUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
post = post_svc.get_post(db, post_id)
|
||
if not post or post.tenant_no != tenant_no:
|
||
return error_response(404, "职位不存在")
|
||
try:
|
||
user_svc.sync_post_user_bindings(
|
||
db, post_id, tenant_no, body.user_ids, user.get("user_id")
|
||
)
|
||
return success_response(None, message="保存成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 菜单管理
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class MenuCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
menu_name: str = Field(..., alias="menuName")
|
||
parent_id: str = Field("0", alias="parentId")
|
||
menu_type: str = Field("M", alias="menuType")
|
||
path: Optional[str] = Field("", alias="path")
|
||
component: Optional[str] = Field(None, alias="component")
|
||
perms: Optional[str] = Field(None, alias="perms")
|
||
is_frame: Optional[int] = Field(1, alias="isFrame")
|
||
visible: Optional[str] = Field("1", alias="visible")
|
||
status: Optional[str] = Field("1", alias="status")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class MenuUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
menu_name: Optional[str] = Field(None, alias="menuName")
|
||
parent_id: Optional[str] = Field(None, alias="parentId")
|
||
menu_type: Optional[str] = Field(None, alias="menuType")
|
||
path: Optional[str] = Field(None, alias="path")
|
||
component: Optional[str] = Field(None, alias="component")
|
||
perms: Optional[str] = Field(None, alias="perms")
|
||
is_frame: Optional[int] = Field(None, alias="isFrame")
|
||
visible: Optional[str] = Field(None, alias="visible")
|
||
status: Optional[str] = Field(None, alias="status")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
@router.get("/menus")
|
||
def list_menus(
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
tree = menu_svc.list_menus_tree(db, keyword=keyword, tenant_no=tenant_no, user_info=user)
|
||
return success_response(tree)
|
||
|
||
|
||
|
||
MENU_EXPORT_COLUMNS = [
|
||
ExportColumn(title="菜单名称", field="menuName"),
|
||
ExportColumn(title="菜单类型", field="menuType"),
|
||
ExportColumn(title="路由", field="path"),
|
||
ExportColumn(title="组件", field="component"),
|
||
ExportColumn(title="权限标识", field="perms"),
|
||
ExportColumn(title="状态", field="status", dict_type="sys_status"),
|
||
ExportColumn(title="可见", field="visible"),
|
||
ExportColumn(title="创建时间", field="createTime"),
|
||
]
|
||
|
||
|
||
class MenuExportBody(BaseModel):
|
||
keyword: Optional[str] = None
|
||
ids: Optional[List[str]] = None
|
||
columns: Optional[List[str]] = None
|
||
|
||
|
||
@router.post("/menus/export")
|
||
def export_menus(
|
||
body: MenuExportBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""导出菜单列表(扁平行;当前筛选或已选)。"""
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
# list flat via tree then flatten, or use underlying list if exists
|
||
from app.crud import menu as menu_mod
|
||
if hasattr(menu_mod, 'list_menus'):
|
||
rows = menu_mod.list_menus(db, keyword=body.keyword, tenant_no=tenant_no, user_info=user)
|
||
else:
|
||
tree = menu_svc.list_menus_tree(db, keyword=body.keyword, tenant_no=tenant_no, user_info=user)
|
||
rows = []
|
||
def walk(nodes):
|
||
for n in nodes or []:
|
||
item = {k: v for k, v in n.items() if k != 'children'}
|
||
rows.append(item)
|
||
walk(n.get('children') or [])
|
||
walk(tree)
|
||
if body.ids:
|
||
idset = set(body.ids)
|
||
rows = [r for r in rows if r.get("id") in idset]
|
||
cols = filter_export_columns(MENU_EXPORT_COLUMNS, body.columns)
|
||
date_str = datetime.now().strftime("%Y%m%d")
|
||
return export_rows_to_xlsx_response(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
sheet_title="菜单列表",
|
||
columns=cols,
|
||
rows=rows,
|
||
filename_stem=f"菜单列表_{date_str}",
|
||
)
|
||
except ExportValidationError as e:
|
||
return error_response(400, str(e))
|
||
except Exception as e:
|
||
return error_response(500, str(e))
|
||
|
||
|
||
@router.post("/menus")
|
||
def create_menu(
|
||
body: MenuCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = menu_svc.create_menu(
|
||
db,
|
||
menu_name=body.menu_name,
|
||
parent_id=body.parent_id,
|
||
menu_type=body.menu_type,
|
||
path=body.path,
|
||
component=body.component,
|
||
perms=body.perms,
|
||
is_frame=body.is_frame,
|
||
visible=body.visible,
|
||
status=body.status,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(menu_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/menus/{menu_id}")
|
||
def update_menu(
|
||
menu_id: str,
|
||
body: MenuUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = menu_svc.update_menu(
|
||
db,
|
||
menu_id,
|
||
menu_name=payload.get("menu_name"),
|
||
parent_id=payload.get("parent_id"),
|
||
menu_type=payload.get("menu_type"),
|
||
path=payload.get("path"),
|
||
component=payload.get("component"),
|
||
perms=payload.get("perms"),
|
||
is_frame=payload.get("is_frame"),
|
||
visible=payload.get("visible"),
|
||
status=payload.get("status"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(menu_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/menus/{menu_id}")
|
||
def delete_menu(
|
||
menu_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
menu_svc.soft_delete_menu(db, menu_id, user_info=_user)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 角色管理
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class RoleCreateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
role_name: str = Field(..., alias="roleName")
|
||
data_scope: Optional[str] = Field("4", alias="dataScope")
|
||
status: Optional[str] = Field("1", alias="status")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class RoleUpdateBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
role_name: Optional[str] = Field(None, alias="roleName")
|
||
data_scope: Optional[str] = Field(None, alias="dataScope")
|
||
status: Optional[str] = Field(None, alias="status")
|
||
order_num: Optional[int] = Field(None, alias="orderNum")
|
||
remark: Optional[str] = None
|
||
|
||
|
||
class RoleMenuBody(BaseModel):
|
||
menu_ids: List[str] = Field(..., alias="menuIds")
|
||
|
||
|
||
class RoleDataScopeBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
data_scope: str = Field(..., alias="dataScope")
|
||
scope_details: Optional[List[dict]] = Field(None, alias="scopeDetails")
|
||
|
||
|
||
@router.get("/role-management")
|
||
def list_role_management(
|
||
request: Request,
|
||
page: int = Query(1, ge=1),
|
||
pageSize: int = Query(10, ge=1, le=500, alias="pageSize"),
|
||
keyword: Optional[str] = None,
|
||
user: dict = Depends(require_menu_code("system:role:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, total = role_svc.list_roles(
|
||
db,
|
||
page=page,
|
||
page_size=pageSize,
|
||
keyword=keyword,
|
||
role_name=request.query_params.get("roleName"),
|
||
status=request.query_params.get("status"),
|
||
status_in=request.query_params.get("statusIn"),
|
||
tenant_no=tenant_no,
|
||
user_info=user,
|
||
)
|
||
tp = role_svc.total_pages(total, pageSize)
|
||
return success_response({"list": items, "total": total, "page": page, "pageSize": pageSize, "totalPages": tp})
|
||
|
||
|
||
|
||
ROLE_EXPORT_COLUMNS = [
|
||
ExportColumn(title="角色名称", field="roleName"),
|
||
ExportColumn(title="数据权限", field="dataScope", dict_type="sys_data_scope"),
|
||
ExportColumn(title="状态", field="status", dict_type="sys_status"),
|
||
ExportColumn(title="创建时间", field="createTime"),
|
||
]
|
||
|
||
|
||
class RoleExportBody(BaseModel):
|
||
keyword: Optional[str] = None
|
||
ids: Optional[List[str]] = None
|
||
columns: Optional[List[str]] = None
|
||
|
||
|
||
@router.post("/role-management/export")
|
||
def export_role_management(
|
||
body: RoleExportBody,
|
||
user: dict = Depends(require_menu_code("system:role:list")),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""导出角色列表(当前筛选全量,或 ids 已选)。"""
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
items, _ = role_svc.list_roles(
|
||
db,
|
||
page=1,
|
||
page_size=EXPORT_MAX_ROWS,
|
||
keyword=body.keyword,
|
||
tenant_no=tenant_no,
|
||
ids=body.ids,
|
||
max_page_size=EXPORT_MAX_ROWS,
|
||
user_info=user,
|
||
)
|
||
cols = filter_export_columns(ROLE_EXPORT_COLUMNS, body.columns)
|
||
date_str = datetime.now().strftime("%Y%m%d")
|
||
return export_rows_to_xlsx_response(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
sheet_title="角色列表",
|
||
columns=cols,
|
||
rows=items,
|
||
filename_stem=f"角色列表_{date_str}",
|
||
)
|
||
except ExportValidationError as e:
|
||
return error_response(400, str(e))
|
||
except Exception as e:
|
||
return error_response(500, str(e))
|
||
|
||
|
||
@router.post("/role-management")
|
||
def create_role_management(
|
||
body: RoleCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = role_svc.create_role(
|
||
db,
|
||
role_name=body.role_name,
|
||
data_scope=body.data_scope,
|
||
status=body.status,
|
||
tenant_no=tenant_no,
|
||
order_num=body.order_num,
|
||
remark=body.remark,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(role_svc._row_to_dict(obj), message="创建成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.get("/role-management/{role_id}")
|
||
def get_role_management(
|
||
role_id: str,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
tenant_no = _get_tenant_no(db, user)
|
||
obj = role_svc.get_role(db, role_id)
|
||
if not obj or obj.tenant_no != tenant_no:
|
||
return error_response(404, "角色不存在")
|
||
try:
|
||
assert_row_in_data_scope(db, obj, SysRole, user, tenant_no)
|
||
except HTTPException:
|
||
return error_response(403, "无权查看该角色")
|
||
return success_response(role_svc._row_to_dict(obj))
|
||
|
||
|
||
@router.put("/role-management/{role_id}")
|
||
def update_role_management(
|
||
role_id: str,
|
||
body: RoleUpdateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
payload: dict[str, Any] = body.model_dump(exclude_unset=True, by_alias=False)
|
||
obj = role_svc.update_role(
|
||
db,
|
||
role_id,
|
||
role_name=payload.get("role_name"),
|
||
data_scope=payload.get("data_scope"),
|
||
status=payload.get("status"),
|
||
order_num=payload.get("order_num"),
|
||
remark=payload.get("remark"),
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(role_svc._row_to_dict(obj), message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/role-management/{role_id}")
|
||
def delete_role_management(
|
||
role_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
role_svc.soft_delete_role(db, role_id, user_info=_user)
|
||
return success_response(None, message="删除成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.get("/role-management/{role_id}/menus")
|
||
def get_role_menus(
|
||
role_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
menu_ids = role_svc.get_role_menu_ids(db, role_id)
|
||
return success_response({"menuIds": menu_ids})
|
||
|
||
|
||
@router.put("/role-management/{role_id}/menus")
|
||
def update_role_menus(
|
||
role_id: str,
|
||
body: RoleMenuBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
role_svc.update_role_menus(db, role_id, body.menu_ids, tenant_no, user_id=user.get("user_id"))
|
||
return success_response(None, message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.get("/role-management/{role_id}/data-scope")
|
||
def get_role_data_scope(
|
||
role_id: str,
|
||
_user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
role = role_svc.get_role(db, role_id)
|
||
if not role:
|
||
return error_response(404, "角色不存在")
|
||
details = role_svc.get_role_data_scope(db, role_id)
|
||
return success_response({"dataScope": role.data_scope or "1", "scopeDetails": details})
|
||
|
||
|
||
@router.put("/role-management/{role_id}/data-scope")
|
||
def update_role_data_scope(
|
||
role_id: str,
|
||
body: RoleDataScopeBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
try:
|
||
tenant_no = _get_tenant_no(db, user)
|
||
role_svc.update_role_data_scope(
|
||
db,
|
||
role_id,
|
||
body.data_scope,
|
||
body.scope_details,
|
||
tenant_no=tenant_no,
|
||
user_id=user.get("user_id"),
|
||
)
|
||
return success_response(None, message="更新成功")
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 附件上传
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def _format_file_size(size_bytes: int) -> str:
|
||
if size_bytes < 1024:
|
||
return f"{size_bytes} B"
|
||
elif size_bytes < 1024 * 1024:
|
||
return f"{size_bytes / 1024:.1f} KB"
|
||
else:
|
||
return f"{size_bytes / (1024 * 1024):.1f} MB"
|
||
|
||
|
||
@router.post("/attachment/upload")
|
||
def upload_attachment(
|
||
file: UploadFile = File(...),
|
||
folder: Optional[str] = Form(""),
|
||
biz_type: Optional[str] = Form(None, alias="bizType"),
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
if not file or not file.filename:
|
||
return error_response(400, "请选择文件")
|
||
try:
|
||
effective_folder = resolve_upload_folder(
|
||
folder=folder or "", biz_type=biz_type or ""
|
||
)
|
||
file_content = file.file.read()
|
||
full_url = storage.upload_file(file_content, file.filename, effective_folder)
|
||
base_url = settings.MINIO_BASE_URL.rstrip("/") if settings.MINIO_BASE_URL else ""
|
||
if base_url and full_url.startswith(base_url + "/"):
|
||
file_url = full_url[len(base_url) + 1:]
|
||
else:
|
||
file_url = full_url
|
||
file_size = len(file_content)
|
||
file_ext = os.path.splitext(file.filename)[1].lstrip(".") if file.filename else ""
|
||
file_type = file.content_type or ""
|
||
tenant_no = _get_tenant_no(db, user)
|
||
record = oss_svc.create_oss(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
url=file_url,
|
||
original_name=file.filename,
|
||
file_suffix=file_ext,
|
||
file_size=file_size,
|
||
file_type=file_type,
|
||
biz_type=(biz_type or "").strip(),
|
||
user_id=user.get("user_id"),
|
||
ext1=effective_folder or None,
|
||
)
|
||
return success_response({
|
||
"id": record.id,
|
||
"fileName": record.original_name,
|
||
"fileUrl": record.url,
|
||
"fileSize": record.file_size,
|
||
"fileSizeDisplay": _format_file_size(record.file_size or 0),
|
||
"fileType": record.file_type,
|
||
"bizType": record.biz_type or "",
|
||
})
|
||
except Exception as e:
|
||
db.rollback()
|
||
return error_response(500, f"上传失败: {str(e)}")
|
||
|
||
|
||
class AttachmentDownloadUrlBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
file_url: str = Field(..., alias="fileUrl")
|
||
file_name: Optional[str] = Field(None, alias="fileName")
|
||
|
||
|
||
@router.post("/attachment/download-url")
|
||
def attachment_download_url(
|
||
body: AttachmentDownloadUrlBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""返回带 Content-Disposition 的下载 URL,保证浏览器使用原始文件名。"""
|
||
file_url = (body.file_url or "").strip()
|
||
if not file_url:
|
||
return error_response(400, "文件地址不能为空")
|
||
tenant_no = _get_tenant_no(db, user)
|
||
file_url, att = oss_svc.resolve_storage_path(db, tenant_no, file_url)
|
||
if not att:
|
||
att = oss_svc.get_oss_by_url(db, tenant_no, file_url)
|
||
download_name = (body.file_name or "").strip()
|
||
if not download_name and att:
|
||
download_name = (att.original_name or "").strip()
|
||
if not download_name:
|
||
download_name = file_url.rsplit("/", 1)[-1] or "download"
|
||
try:
|
||
url = storage.presigned_download_url(file_url, download_name)
|
||
return success_response({"downloadUrl": url})
|
||
except Exception as e:
|
||
return error_response(500, f"获取下载链接失败: {str(e)}")
|
||
|
||
|
||
@router.post("/attachment/preview-url")
|
||
def attachment_preview_url(
|
||
body: AttachmentDownloadUrlBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""返回用于浏览器内联预览的 URL(PDF iframe 等)。"""
|
||
file_url = (body.file_url or "").strip()
|
||
if not file_url:
|
||
return error_response(400, "文件地址不能为空")
|
||
tenant_no = _get_tenant_no(db, user)
|
||
file_url, _att = oss_svc.resolve_storage_path(db, tenant_no, file_url)
|
||
try:
|
||
url = storage.presigned_preview_url(file_url)
|
||
return success_response({"previewUrl": url})
|
||
except Exception as e:
|
||
return error_response(500, f"获取预览链接失败: {str(e)}")
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 用户菜单收藏
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
@router.get("/menu-favorites")
|
||
def list_menu_favorites(
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
user_id = user.get("user_id")
|
||
if not user_id:
|
||
return error_response(401, "用户无效")
|
||
tenant_no = _get_tenant_no(db, user)
|
||
from app.crud import menu_favorite as fav_svc
|
||
|
||
return success_response(fav_svc.list_favorites(db, user_id=user_id, tenant_no=tenant_no))
|
||
|
||
|
||
@router.post("/menu-favorites")
|
||
def create_menu_favorite(
|
||
body: MenuFavoriteCreateBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
from app.crud import menu_favorite as fav_svc
|
||
|
||
user_id = user.get("user_id")
|
||
if not user_id:
|
||
return error_response(401, "用户无效")
|
||
tenant_no = _get_tenant_no(db, user)
|
||
try:
|
||
data = fav_svc.add_favorite(
|
||
db,
|
||
user_id=user_id,
|
||
tenant_no=tenant_no,
|
||
menu_path=body.path,
|
||
create_dept_id=str(user.get("dept_id") or ""),
|
||
)
|
||
return success_response(data)
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.put("/menu-favorites/order")
|
||
def reorder_menu_favorites(
|
||
body: MenuFavoriteReorderBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
from app.crud import menu_favorite as fav_svc
|
||
|
||
user_id = user.get("user_id")
|
||
if not user_id:
|
||
return error_response(401, "用户无效")
|
||
tenant_no = _get_tenant_no(db, user)
|
||
try:
|
||
data = fav_svc.reorder_favorites(
|
||
db, user_id=user_id, tenant_no=tenant_no, paths=body.paths or []
|
||
)
|
||
return success_response(data)
|
||
except ValueError as e:
|
||
return error_response(400, str(e))
|
||
|
||
|
||
@router.delete("/menu-favorites")
|
||
def delete_menu_favorite(
|
||
body: MenuFavoriteDeleteBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
from app.crud import menu_favorite as fav_svc
|
||
|
||
user_id = user.get("user_id")
|
||
if not user_id:
|
||
return error_response(401, "用户无效")
|
||
tenant_no = _get_tenant_no(db, user)
|
||
fav_svc.soft_delete_favorite(
|
||
db, user_id=user_id, tenant_no=tenant_no, menu_path=body.path
|
||
)
|
||
return success_response({"path": body.path})
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Demo 模板页:Mock 数据由前端组装行后走统一 xlsx 导出(无 Demo 业务表)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class DemoXlsxColumnSpec(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
title: str
|
||
field: str
|
||
|
||
|
||
class DemoXlsxExportBody(BaseModel):
|
||
model_config = ConfigDict(populate_by_name=True)
|
||
|
||
sheet_title: str = Field(default="导出", alias="sheetTitle")
|
||
filename_stem: str = Field(default="export", alias="filenameStem")
|
||
columns: List[DemoXlsxColumnSpec]
|
||
rows: List[dict]
|
||
|
||
|
||
@router.post("/demo/xlsx-export")
|
||
def demo_xlsx_export(
|
||
body: DemoXlsxExportBody,
|
||
user: dict = Depends(require_admin_or_super_admin),
|
||
db: Session = Depends(get_db),
|
||
):
|
||
"""Demo 列表导出:接收已筛选行与列定义,生成 xlsx。"""
|
||
try:
|
||
rows = body.rows or []
|
||
if not rows:
|
||
raise ExportValidationError("没有可导出的数据")
|
||
if len(rows) > EXPORT_MAX_ROWS:
|
||
raise ExportValidationError(f"导出超过 {EXPORT_MAX_ROWS} 条,请缩小筛选范围")
|
||
if not body.columns:
|
||
raise ExportValidationError("没有可导出的列")
|
||
cols = [ExportColumn(title=c.title, field=c.field) for c in body.columns]
|
||
tenant_no = _get_tenant_no(db, user)
|
||
date_str = datetime.now().strftime("%Y%m%d")
|
||
stem = (body.filename_stem or "export").strip() or "export"
|
||
return export_rows_to_xlsx_response(
|
||
db,
|
||
tenant_no=tenant_no,
|
||
sheet_title=body.sheet_title or "导出",
|
||
columns=cols,
|
||
rows=rows,
|
||
filename_stem=f"{stem}_{date_str}",
|
||
)
|
||
except ExportValidationError as e:
|
||
return error_response(400, str(e))
|