feat(system): 业务岗位角色管理接口与保护规则

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

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-12 16:39:08 +08:00
1 parent 1b3509b20f
commit 7a0f3f4b98
6 files changed
+590

No files matched your search

+90
View File
@@ -0,0 +1,90 @@
"""业务岗位角色的请求/响应模型。"""
import re
from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.core.response import PageQuery
# 岗位编码规则与系统角色一致:小写字母开头,后接小写字母/数字/下划线,长度 2–32。
# 编码会作为参数维度值(param_values.dimension_value)与核算模板绑定的锚点,
# 保持单一风格避免大小写/全角混用造成对照困难
BUSINESS_ROLE_CODE_PATTERN = re.compile(r"^[a-z][a-z0-9_]{1,31}$")
class BusinessRoleOut(BaseModel):
"""岗位响应(列表与详情共用)。"""
model_config = ConfigDict(from_attributes=True)
id: int
role_code: str
role_name: str
calc_template: str
description: str | None = None
sort_order: int = 0
status: int = 1
is_builtin: int = 0
class TemplateParamOut(BaseModel):
"""模板声明的参数(供岗位表单展示,含当前口径说明)。"""
code: str
name: str
class TemplateOptionOut(BaseModel):
"""公式模板下拉项。"""
code: str
name: str
description: str
params: list[TemplateParamOut]
class BusinessRoleCreateIn(BaseModel):
"""新增岗位。"""
model_config = ConfigDict(str_strip_whitespace=True)
role_code: str = Field(min_length=2, max_length=32, description="岗位编码,创建后不可修改")
role_name: str = Field(min_length=1, max_length=64, description="岗位名称")
calc_template: str = Field(min_length=1, max_length=32, description="计提公式模板编码")
description: str | None = Field(default=None, max_length=255)
sort_order: int = Field(default=0, ge=0, description="显示排序,越小越靠前")
@field_validator("role_code")
@classmethod
def _check_code(cls, value: str) -> str:
if not BUSINESS_ROLE_CODE_PATTERN.match(value):
raise ValueError("岗位编码须以小写字母开头,仅可含小写字母、数字与下划线")
return value
class BusinessRoleUpdateIn(BaseModel):
"""编辑岗位。
**不含 role_code** —— 编码是参数维度值与模板绑定的锚点(同系统角色的保护理由)。
前端表单全量提交,故为全量替换语义。
"""
model_config = ConfigDict(str_strip_whitespace=True)
role_name: str = Field(min_length=1, max_length=64, description="岗位名称")
calc_template: str = Field(min_length=1, max_length=32, description="计提公式模板编码")
description: str | None = Field(default=None, max_length=255)
sort_order: int = Field(default=0, ge=0, description="显示排序,越小越靠前")
class BusinessRoleStatusIn(BaseModel):
"""启用/禁用岗位。"""
status: int = Field(ge=0, le=1, description="1启用 0禁用")
class BusinessRoleQuery(PageQuery):
"""岗位列表查询条件。"""
keyword: str | None = Field(default=None, max_length=64, description="编码或名称,模糊匹配")
status: int | None = Field(default=None, ge=0, le=1, description="状态筛选")