- 后端 /api/v1/dashboard 单端点返回四区块:经营总览(绩效/应付/人数/团队数/月结状态/近 6 个月趋势)、待办(待审批 + 回款未指定批次 + 同步显式降级)、风险预警(回款逾期/审批超时,阈值参数化)、最近动态(操作日志) - dashboard_service 实时派生(不落表、不引调度器);参数快照一次解析;总览只做 SQL 聚合,不复用报表的重查询 - V10 迁移新增预警参数三项(warning.enabled / warning.receipt.overdue.days=90 / warning.approval.timeout.days=3),参数注册表 19→22,init-dev 与 V10 SQL 双轨同步 - 测试:test_dashboard 5 例(权限文案/总览对账+非法月份 400/待办三类/预警命中与参数关闭/动态来源);全量 852 passed - 依赖闭包并入:core/month.py 月份校验、api/v1/base/business_roles.py(router 已挂载的岗位只读端点)、test_month_validation - 修复:alembic --sql 在 Windows 按控制台 GBK 输出,V10 SQL 以 UTF-8 重新导出(README 已记) Co-Authored-By: Claude Code <noreply@anthropic.com>
126 lines
3.9 KiB
Python
126 lines
3.9 KiB
Python
"""工作台的请求/响应模型(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)
|