补齐 code-review 遗留 2 项:审批单此前只固化 ref_id/refer_no 指针,复核者看到 的是业务当前内容;二次分配方案在编辑替换与驳回清空时一律物理删除,方案事实 无处回溯。 - 审批单加 version_no 与 snapshot_json(信封 schema_version/order_type/ captured_at/data);二次分配、月结、调整单三类提交均固化送审内容,开关关闭 的自动通过路径同样写快照;详情按 snapshot/live/missing 三态回显,历史单 只读回退当前内容并显式告警,不回填历史快照 - 二次分配方案行改 append-only 版本化(version_no/is_active,唯一键扩为 business+version+personnel):编辑替换与驳回清空只置历史,核算与详情默认 只取生效行,业务详情新增 allocation_history - 送审内容未变不重提、方案未变不新增版本:判据统一为送审内容,两个 version_no 语义解耦(方案版本=内容第几版,审批单版本=第几次送审) - 审批单列表、工作台待办、审批超时预警三处 defer 快照列,避免整列拉取 - 月结提交取行加 for_update,防并发提交算出重复的审批单版本号 - 修复审批类型筛选上界 le=2(调整单无法筛选)、快照金额把空值写成 0.00 - V11 迁移三件套同步(Alembic / db/migration / init-dev.sql) 测试:单元 +16(方案内容比较 10、审批入参 6)、集成 +12;全量 891 passed。 Co-Authored-By: Claude Code <noreply@anthropic.com>
80 lines
2.5 KiB
Python
80 lines
2.5 KiB
Python
"""审批管理的请求/响应模型(2026-10-15 复核审批补充需求)。"""
|
||
|
||
from datetime import datetime
|
||
from typing import Any, Literal
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||
|
||
from app.core.response import PageQuery
|
||
|
||
|
||
class ApprovalStepOut(BaseModel):
|
||
"""节点记录(时间线;action=3 自动通过 operator 记「系统」)。"""
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
id: int
|
||
node: int
|
||
action: int
|
||
opinion: str | None = None
|
||
operator_id: int | None = None
|
||
operator_name: str
|
||
create_time: datetime
|
||
|
||
|
||
class ApprovalOrderOut(BaseModel):
|
||
"""审批单列表行。"""
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
id: int
|
||
order_no: str
|
||
order_type: int
|
||
ref_id: int
|
||
refer_no: str
|
||
version_no: int
|
||
status: int
|
||
submitter_id: int | None = None
|
||
submitter_name: str
|
||
create_time: datetime
|
||
|
||
|
||
class ApprovalOrderDetailOut(ApprovalOrderOut):
|
||
"""审批单详情:附节点时间线与审批内容。
|
||
|
||
``content_source`` 语义:``snapshot``=提交时快照(可信);
|
||
``live``=历史单无快照、回退读当前业务内容(仅供参考);
|
||
``missing``=引用对象已不存在且无快照。
|
||
"""
|
||
|
||
steps: list[ApprovalStepOut] = Field(default_factory=list)
|
||
snapshot: dict[str, Any] | None = Field(
|
||
default=None, description="提交时内容快照(信封:schema_version/order_type/captured_at/data)"
|
||
)
|
||
content_source: Literal["snapshot", "live", "missing"] = Field(
|
||
default="missing", description="审批内容来源:提交时快照/当前内容回退/缺失"
|
||
)
|
||
|
||
|
||
class ApprovalQuery(PageQuery):
|
||
"""审批单列表查询条件。"""
|
||
|
||
keyword: str | None = Field(default=None, max_length=32, description="审批单号/引用单号,模糊")
|
||
status: int | None = Field(default=None, ge=1, le=5, description="状态筛选")
|
||
order_type: int | None = Field(default=None, ge=1, le=3, description="对象类型筛选")
|
||
|
||
|
||
class ApprovalActionIn(BaseModel):
|
||
"""节点动作:1 通过 / 2 驳回(驳回须填意见——空驳无从追溯)。"""
|
||
|
||
model_config = ConfigDict(str_strip_whitespace=True)
|
||
|
||
action: int = Field(ge=1, le=2, description="1通过 2驳回")
|
||
opinion: str | None = Field(default=None, max_length=255, description="审批意见")
|
||
|
||
@model_validator(mode="after")
|
||
def _check_opinion(self) -> "ApprovalActionIn":
|
||
if self.action == 2 and not self.opinion:
|
||
raise ValueError("驳回必须填写审批意见")
|
||
return self
|