PayrollManagementSystem/backend/app/api/v1/system/business_roles.py
liuwenxin 7a0f3f4b98 feat(system): 业务岗位角色管理接口与保护规则
- 四层实现:仓储(FOR UPDATE 锁行 + 引用计数钩子预留)/ schemas / service / api 共 7 个端点
- 保护规则:内置岗位禁删禁停、编码创建后不可改、模板须在注册表内(未注册 400)
- 写操作全走审计上下文;冒烟验证 7 项(含成功/失败双路径落日志)
- 端点:列表/新增/详情/编辑/删除/启停 + meta/templates 模板下拉

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-12 16:39:08 +08:00

123 lines
4.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""业务岗位角色管理接口(需求 #28:岗位可扩展 + 绑定公式模板)。
保护规则(内置岗位禁删禁停、编码不可改、模板须在注册表内)见
`business_role_service` 模块 docstring。前端会禁用相应按钮,但**后端校验是唯一防线**。
全部要求管理员权限;写操作经 `RequestMetaDep` 带入请求元信息由 service
层写入操作日志。
"""
from typing import Annotated
from fastapi import APIRouter, Path, Query
from app.api.deps import AdminUser, DbSession, RequestMetaDep
from app.core.response import ApiResponse, PageResult, ok
from app.schemas.business_role import (
BusinessRoleCreateIn,
BusinessRoleOut,
BusinessRoleQuery,
BusinessRoleStatusIn,
BusinessRoleUpdateIn,
TemplateOptionOut,
)
from app.services import business_role_service
router = APIRouter(prefix="/business-roles", tags=["系统管理 · 岗位角色"])
@router.get("", response_model=ApiResponse[PageResult[BusinessRoleOut]], summary="岗位列表")
def list_roles(
session: DbSession,
_: AdminUser,
query: Annotated[BusinessRoleQuery, Query()],
) -> ApiResponse[PageResult[BusinessRoleOut]]:
"""分页查询业务岗位角色(含停用)。"""
return ok(business_role_service.list_roles(session, query))
@router.post("", response_model=ApiResponse[BusinessRoleOut], summary="新增岗位")
def create_role(
session: DbSession,
current_user: AdminUser,
payload: BusinessRoleCreateIn,
meta: RequestMetaDep,
) -> ApiResponse[BusinessRoleOut]:
"""新增岗位。编码全局唯一、创建后不可修改;模板须在注册表内;新岗位默认启用。"""
return ok(
business_role_service.create_role(session, payload, operator=current_user, meta=meta),
message="岗位已创建",
)
@router.get(
"/meta/templates",
response_model=ApiResponse[list[TemplateOptionOut]],
summary="公式模板下拉",
)
def list_template_options(session: DbSession, _: AdminUser) -> ApiResponse[list[TemplateOptionOut]]:
"""取公式模板下拉(含声明参数,供岗位表单展示)。
定义在 `/{role_id}` 之前:静态路径优先,避免 "meta" 被当作路径参数解析。
"""
return ok(business_role_service.list_template_options())
@router.get("/{role_id}", response_model=ApiResponse[BusinessRoleOut], summary="岗位详情")
def get_role(
session: DbSession,
_: AdminUser,
role_id: Annotated[int, Path(ge=1)],
) -> ApiResponse[BusinessRoleOut]:
"""按主键查询岗位详情。"""
return ok(business_role_service.get_role(session, role_id))
@router.put("/{role_id}", response_model=ApiResponse[BusinessRoleOut], summary="编辑岗位")
def update_role(
session: DbSession,
current_user: AdminUser,
role_id: Annotated[int, Path(ge=1)],
payload: BusinessRoleUpdateIn,
meta: RequestMetaDep,
) -> ApiResponse[BusinessRoleOut]:
"""编辑岗位名称/模板/说明/排序。
**不含 role_code** —— 编码是参数维度值与模板绑定的锚点。
"""
return ok(
business_role_service.update_role(
session, role_id, payload, operator=current_user, meta=meta
),
message="已保存",
)
@router.put(
"/{role_id}/status", response_model=ApiResponse[BusinessRoleOut], summary="启用/禁用岗位"
)
def set_status(
session: DbSession,
current_user: AdminUser,
role_id: Annotated[int, Path(ge=1)],
payload: BusinessRoleStatusIn,
meta: RequestMetaDep,
) -> ApiResponse[BusinessRoleOut]:
"""启用或禁用岗位。内置岗位不可停用。"""
result = business_role_service.set_status(
session, role_id, payload.status, operator=current_user, meta=meta
)
return ok(result, message="已启用" if payload.status == 1 else "已禁用")
@router.delete("/{role_id}", response_model=ApiResponse[None], summary="删除岗位")
def delete_role(
session: DbSession,
current_user: AdminUser,
role_id: Annotated[int, Path(ge=1)],
meta: RequestMetaDep,
) -> ApiResponse[None]:
"""删除岗位。内置岗位不可删除;有人员引用的岗位不可删除。"""
business_role_service.delete_role(session, role_id, operator=current_user, meta=meta)
return ok(message="岗位已删除")