"""审批管理的请求/响应模型(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