"""工作台的请求/响应模型(M4 ⑧)。 四区块一次返回(前端一次请求渲染整页,避免 6-10 次往返): 经营绩效总览 / 待办事项 / 风险预警 / 最近动态。金额一律 Decimal (序列化为字符串),前端不做数值运算。 待办与预警为**查询时实时派生**(不落库、不引调度器,见计划决策); ⑥ T+ 同步未建前,同步相关条目用 `sync_available=false` 显式降级, 不返回伪造的 0 条。 """ from datetime import date, datetime from decimal import Decimal from pydantic import BaseModel, Field class TrendPoint(BaseModel): """近 6 个月趋势点(绩效按台账归属月,回款按到账月)。""" month: str performance_total: Decimal | None = None receipt_total: Decimal | None = None class SettlementBrief(BaseModel): """当月月结状态(工作台总览角标)。""" exists: bool = False status: int | None = Field(default=None, description="1草稿 2待审批 3已审定 4已驳回") total_performance: Decimal | None = None total_payable: Decimal | None = None locked_at: datetime | None = None class OverviewBlock(BaseModel): """经营绩效总览(FR-033):口径与台账/月结一致。""" month: str performance_total: Decimal | None = Field(default=None, description="当月单笔绩效合计") payable_total: Decimal | None = Field(default=None, description="当月应付绩效合计") person_count: int = Field(default=0, description="当月有台账的人员数(拆行去重)") team_count: int = Field(default=0, description="在职营销经理数(团队数)") settlement: SettlementBrief trend: list[TrendPoint] = Field(default_factory=list, description="近 6 个月") note: str = Field(default="", description="口径说明(页面展示)") class ApprovalTodoItem(BaseModel): """待审批条目。""" order_id: int order_no: str order_type: int submitter_name: str | None = None create_time: datetime class ReceiptTodoItem(BaseModel): """回款未指定发货批次条目。""" receipt_id: int receipt_batch_no: str business_id: int business_no: str | None = None amount: Decimal receipt_date: date class TodoBlock(BaseModel): """待办事项(FR-034,澄清 #11 起步范围)。""" pending_approval_total: int = 0 pending_approvals: list[ApprovalTodoItem] = Field(default_factory=list) unassigned_receipt_total: int = 0 unassigned_receipts: list[ReceiptTodoItem] = Field(default_factory=list) sync_available: bool = Field(default=False, description="⑥ 同步模块是否已启用") sync_failure_total: int = 0 class WarningItem(BaseModel): """风险预警条目(实时派生,不改变业务数据)。""" code: str = Field(description="规则编码:receipt_overdue / approval_timeout") level: str = Field(description="级别:warning / danger") object_type: str object_id: int title: str detail: str suggest: str link: str | None = Field(default=None, description="前端跳转路径") class WarningBlock(BaseModel): """风险预警(FR-035,首批两条可算规则;同步规则待 ⑥)。""" enabled: bool = True receipt_overdue_days: int = 90 approval_timeout_days: int = 3 items: list[WarningItem] = Field(default_factory=list) receipt_overdue_total: int = 0 approval_timeout_total: int = 0 sync_available: bool = False class ActivityItem(BaseModel): """最近动态条目(来源 operation_logs,只读聚合)。""" id: int module: str action: str summary: str username: str | None = None success: int create_time: datetime class DashboardOut(BaseModel): """工作台四区块一次性响应。""" month: str overview: OverviewBlock todos: TodoBlock warnings: WarningBlock activities: list[ActivityItem] = Field(default_factory=list)