PayrollManagementSystem/backend/app/api/v1/system/params.py
liuwenxin 7008de0c69 feat(api): 计提参数查看放开登录用户、修改仅管理员(#17/#6 答复)
- deps 新增 LoginUser 依赖;params 只读七端点改登录即可,写(提交/撤回)保持 AdminUser
- 前端 params 路由 requiresAdmin → allowedRoles(admin/finance/leader);页面写按钮按角色显隐
- 权限用例分层:普通用户目录 200、提交/撤回 403、未登录拦截

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

149 lines
5.6 KiB
Python
Raw 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:参数配置化 + 版本管理 + 留痕)。
权限分层(澄清 #17 业务答复 2026-10-19「查看可、修改仅管理员」):只读
端点(目录/变更记录/批次明细/对比/导出/预览/历史)要求登录即可;写操作
(提交/撤回)仅管理员。写操作经 `RequestMetaDep` 带入请求元信息写入操作
日志,变更批次本身另行留痕(变更人/时间/项/旧值/新值/生效日期)——双保险。
导出为文件流(非统一信封),前端以 blob 接收。
"""
from datetime import date
from typing import Annotated
from fastapi import APIRouter, Path, Query
from fastapi.responses import StreamingResponse
from app.api.deps import AdminUser, DbSession, LoginUser, RequestMetaDep
from app.core.response import ApiResponse, PageResult, ok
from app.schemas.param import (
ParamBatchDetailOut,
ParamBatchOut,
ParamBatchQuery,
ParamCatalogItemOut,
ParamChangeBatchOut,
ParamChangeIn,
ParamDiffOut,
ParamHistoryOut,
ParamHistoryQuery,
ParamPreviewIn,
ParamPreviewOut,
)
from app.services import param_service
router = APIRouter(prefix="/params", tags=["系统管理 · 计提参数"])
@router.get(
"", response_model=ApiResponse[list[ParamCatalogItemOut]], summary="参数目录(含当前生效值)"
)
def get_catalog(
session: DbSession,
_: LoginUser,
as_of: Annotated[date | None, Query(description="业务日期,默认今日")] = None,
) -> ApiResponse[list[ParamCatalogItemOut]]:
"""全部启用参数的定义元数据与截至 asOf 的生效值(前端按分组展示)。"""
return ok(param_service.get_catalog(session, as_of=as_of))
@router.post("/changes", response_model=ApiResponse[ParamChangeBatchOut], summary="提交变更批次")
def submit_change(
session: DbSession,
current_user: AdminUser,
payload: ParamChangeIn,
meta: RequestMetaDep,
) -> ApiResponse[ParamChangeBatchOut]:
"""提交一批参数变更(1~50 项),返回版本号;立即生效或指定日期生效。"""
return ok(
param_service.submit_change(session, payload, operator=current_user, meta=meta),
message="变更已提交",
)
@router.get(
"/changes", response_model=ApiResponse[PageResult[ParamBatchOut]], summary="变更记录分页"
)
def list_changes(
session: DbSession,
_: LoginUser,
query: Annotated[ParamBatchQuery, Query()],
) -> ApiResponse[PageResult[ParamBatchOut]]:
"""按生效日期区间分页查询变更批次(创建时间倒序)。"""
return ok(param_service.list_batches(session, query))
@router.get(
"/changes/{batch_id}", response_model=ApiResponse[ParamBatchDetailOut], summary="批次明细"
)
def get_batch(
session: DbSession,
_: LoginUser,
batch_id: Annotated[int, Path(ge=1)],
) -> ApiResponse[ParamBatchDetailOut]:
"""批次头 + 逐项旧值 → 新值。"""
return ok(param_service.get_batch_detail(session, batch_id))
@router.delete("/changes/{batch_id}", response_model=ApiResponse[None], summary="撤回未生效批次")
def withdraw_batch(
session: DbSession,
current_user: AdminUser,
batch_id: Annotated[int, Path(ge=1)],
meta: RequestMetaDep,
) -> ApiResponse[None]:
"""撤回尚未生效的批次;已生效批次不可撤回。"""
param_service.withdraw_batch(session, batch_id, operator=current_user, meta=meta)
return ok(message="批次已撤回")
@router.get("/compare", response_model=ApiResponse[list[ParamDiffOut]], summary="两日期快照对比")
def compare(
session: DbSession,
_: LoginUser,
from_date: Annotated[date, Query(description="起始业务日期(含)")],
to_date: Annotated[date, Query(description="截止业务日期(含)")],
) -> ApiResponse[list[ParamDiffOut]]:
"""两个业务日期的全量参数快照逐项对比,changed 标记是否变化。"""
return ok(param_service.compare(session, from_date=from_date, to_date=to_date))
@router.get("/export", summary="导出变更记录 Excel")
def export_changes(
session: DbSession,
_: LoginUser,
start_date: Annotated[date | None, Query(description="生效日期起(含)")] = None,
end_date: Annotated[date | None, Query(description="生效日期止(含)")] = None,
) -> StreamingResponse:
"""变更记录导出为 Excel 文件流(非统一信封,前端以 blob 接收)。"""
content = param_service.export_changes(session, start_date=start_date, end_date=end_date)
filename = f"param_changes_{date.today():%Y%m%d}.xlsx"
return StreamingResponse(
iter([content]),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
)
@router.post("/preview", response_model=ApiResponse[ParamPreviewOut], summary="计算预览")
def preview(
session: DbSession,
_: LoginUser,
payload: ParamPreviewIn,
) -> ApiResponse[ParamPreviewOut]:
"""输入业务场景(岗位/产品/发货/回款/金额),按回款到账日取参数实时试算。"""
return ok(param_service.preview(session, payload))
@router.get(
"/{param_code}/history",
response_model=ApiResponse[PageResult[ParamHistoryOut]],
summary="参数版本历史",
)
def get_history(
session: DbSession,
_: LoginUser,
param_code: Annotated[str, Path(max_length=64)],
query: Annotated[ParamHistoryQuery, Query()],
) -> ApiResponse[PageResult[ParamHistoryOut]]:
"""单参数的版本历史(含版本号、变更人与旧值 → 新值),最新在前。"""
return ok(param_service.get_history(session, param_code, query))