"""计提参数管理接口(需求 #28:参数配置化 + 版本管理 + 留痕)。 全部要求管理员权限(开放问题 #5 的当前默认,后续可放宽只读); 写操作(提交/撤回)经 `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, 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, _: AdminUser, 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, _: AdminUser, 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, _: AdminUser, 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, _: AdminUser, 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, _: AdminUser, 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, _: AdminUser, 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, _: AdminUser, 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))