feat: 新增预算后端服务与差旅风险规则库
后端新增预算模型、端点和服务模块,支持预算 CRUD 和余额 查询,清理旧生成规则文件并替换为按严重等级分类的差旅风 险规则库,优化认证权限和报销单访问策略,新增财务规则目 录和演示数据构建脚本,前端预算中心增加对话框交互,完善 审计页面运行时模型和元数据展示,补充单元测试。
This commit is contained in:
@@ -1,30 +1,31 @@
|
||||
from collections.abc import Generator
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, Header, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db.session import get_session_factory
|
||||
|
||||
|
||||
def get_db() -> Generator[Session, None, None]:
|
||||
db = get_session_factory()()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
from collections.abc import Generator
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, Header, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db.session import get_session_factory
|
||||
|
||||
|
||||
def get_db() -> Generator[Session, None, None]:
|
||||
db = get_session_factory()()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class CurrentUserContext:
|
||||
username: str
|
||||
name: str
|
||||
role_codes: list[str]
|
||||
is_admin: bool
|
||||
department_name: str = ""
|
||||
|
||||
|
||||
cost_center: str = ""
|
||||
|
||||
|
||||
def get_current_user(
|
||||
x_auth_username: Annotated[
|
||||
str | None,
|
||||
@@ -46,34 +47,75 @@ def get_current_user(
|
||||
str | None,
|
||||
Header(description="当前登录人的所属部门。"),
|
||||
] = None,
|
||||
x_auth_cost_center: Annotated[
|
||||
str | None,
|
||||
Header(description="当前登录人的成本中心。"),
|
||||
] = None,
|
||||
) -> CurrentUserContext:
|
||||
role_codes = [item.strip() for item in (x_auth_role_codes or "").split(",") if item.strip()]
|
||||
is_admin = str(x_auth_is_admin or "").strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
username = (x_auth_username or "").strip()
|
||||
name = (x_auth_name or username).strip()
|
||||
|
||||
if not username and not name:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="请先登录后再访问知识库。",
|
||||
)
|
||||
|
||||
return CurrentUserContext(
|
||||
username=username or name,
|
||||
role_codes = [
|
||||
_normalize_role_code(item)
|
||||
for item in (x_auth_role_codes or "").split(",")
|
||||
if _normalize_role_code(item)
|
||||
]
|
||||
username = (x_auth_username or "").strip()
|
||||
name = (x_auth_name or username).strip()
|
||||
is_admin = _resolve_platform_admin_flag(
|
||||
username=username,
|
||||
name=name,
|
||||
role_codes=role_codes,
|
||||
header_value=x_auth_is_admin,
|
||||
)
|
||||
|
||||
if not username and not name:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="请先登录后再访问知识库。",
|
||||
)
|
||||
|
||||
return CurrentUserContext(
|
||||
username=username or name,
|
||||
name=name or username,
|
||||
role_codes=role_codes,
|
||||
is_admin=is_admin,
|
||||
department_name=(x_auth_department or "").strip(),
|
||||
cost_center=(x_auth_cost_center or "").strip(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
def _normalize_role_code(value: str | None) -> str:
|
||||
role_code = str(value or "").strip().lower()
|
||||
if role_code == "auditor":
|
||||
return "budget_monitor"
|
||||
return role_code
|
||||
|
||||
|
||||
def _current_user_role_codes(current_user: CurrentUserContext) -> set[str]:
|
||||
return {_normalize_role_code(item) for item in current_user.role_codes if _normalize_role_code(item)}
|
||||
|
||||
|
||||
def _resolve_platform_admin_flag(
|
||||
*,
|
||||
username: str,
|
||||
name: str,
|
||||
role_codes: list[str],
|
||||
header_value: str | None,
|
||||
) -> bool:
|
||||
if str(header_value or "").strip().lower() in {"1", "true", "yes", "on"}:
|
||||
return True
|
||||
|
||||
identities = {
|
||||
str(username or "").strip().lower(),
|
||||
str(name or "").strip().lower(),
|
||||
}
|
||||
return "admin" in identities or "admin" in {_normalize_role_code(item) for item in role_codes}
|
||||
|
||||
|
||||
def require_admin_user(
|
||||
current_user: Annotated[CurrentUserContext, Depends(get_current_user)],
|
||||
) -> CurrentUserContext:
|
||||
if current_user.is_admin or "manager" in current_user.role_codes:
|
||||
if current_user.is_admin or "manager" in _current_user_role_codes(current_user):
|
||||
return current_user
|
||||
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="只有管理员可以上传、删除或修改知识库文件。",
|
||||
@@ -95,24 +137,58 @@ def require_platform_admin_user(
|
||||
def require_rule_editor_user(
|
||||
current_user: Annotated[CurrentUserContext, Depends(get_current_user)],
|
||||
) -> CurrentUserContext:
|
||||
role_codes = {item.strip() for item in current_user.role_codes}
|
||||
role_codes = _current_user_role_codes(current_user)
|
||||
if current_user.is_admin or "manager" in role_codes or "finance" in role_codes:
|
||||
return current_user
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="只有财务人员或高级管理人员可以编辑规则草稿。",
|
||||
detail="只有财务人员或高级财务人员可以编辑规则草稿。",
|
||||
)
|
||||
|
||||
|
||||
def require_rule_reviewer_user(
|
||||
current_user: Annotated[CurrentUserContext, Depends(get_current_user)],
|
||||
) -> CurrentUserContext:
|
||||
role_codes = {item.strip() for item in current_user.role_codes}
|
||||
role_codes = _current_user_role_codes(current_user)
|
||||
if current_user.is_admin or "manager" in role_codes:
|
||||
return current_user
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="只有高级管理人员或 admin 管理员可以执行该操作。",
|
||||
detail="只有高级财务人员或 admin 管理员可以执行该操作。",
|
||||
)
|
||||
|
||||
|
||||
def require_budget_viewer_user(
|
||||
current_user: Annotated[CurrentUserContext, Depends(get_current_user)],
|
||||
) -> CurrentUserContext:
|
||||
role_codes = _current_user_role_codes(current_user)
|
||||
if current_user.is_admin or role_codes & {"budget_monitor", "executive"}:
|
||||
return current_user
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="只有预算监控员或高级财务人员可以查看预算中心。",
|
||||
)
|
||||
|
||||
|
||||
def require_budget_editor_user(
|
||||
current_user: Annotated[CurrentUserContext, Depends(get_current_user)],
|
||||
) -> CurrentUserContext:
|
||||
role_codes = _current_user_role_codes(current_user)
|
||||
if current_user.is_admin or "executive" in role_codes:
|
||||
return current_user
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="只有 admin 管理员或高级财务人员可以维护预算额度。",
|
||||
)
|
||||
|
||||
|
||||
def is_budget_scope_limited_user(current_user: CurrentUserContext) -> bool:
|
||||
if current_user.is_admin:
|
||||
return False
|
||||
|
||||
role_codes = _current_user_role_codes(current_user)
|
||||
return "budget_monitor" in role_codes and "executive" not in role_codes
|
||||
|
||||
Reference in New Issue
Block a user