# -*- coding: utf-8 -*- """预算状态机口径 —— 单一事实源(2026-10-03,施工单 CMA-20261003 阶段 1) 设计件:`docs/budget-module-governance-design.md` §1 口径(逐字固化): | status | 语义 | 入执行面 | |---|---|---| | `draft` | 草稿(编制中,可编辑) | 否 | | `submitted` | 待审批 | 否 | | `approved` | **已批准(= 执行基准 / 生效版)** | 是 | | `active` | 已下达(下达动作后)/ 历史遗留「在用」 | 是 | | `rejected` | 已驳回 | 否 | | `archived` | 已归档 | 否 | - **执行/对比/差异的唯一基准 = `approved` ∪ `active`**:审批通过即获得执行资格, 修的是「执行查询只读 `active` ⇒ 已批准预算从执行视图消失」这一处口径错配。 - 同键 `(kpi_id, period)` 多行并存时选行优先级:`active`(已下达)> `approved`, 同级取 `updated_at` 最新 —— 保证取行确定性(改前 `bp_map[key] = value` 后写覆盖先写, 结果依赖 DB 返回顺序)。 - 既有 216 行 `active` 本单**不改标**(口径彻底归一属待 finance/任总确认项,见设计件 §1.3); `active` 在 UI 侧仍映射为 `draft`(历史兼容,`approval_status_of`)。 **规则**:任何「预算执行基准」查询必须走 `effective_plan_clause()` / `pick_effective()`,**禁止再手写 `BudgetPlan.status == "active"`**。 非执行面用法(新建去重、自动分解、滚动延展、方法应用、现金流联动) 不在本口径范围内,随「彻底归一」单一起收敛(设计件 §1.5)。 """ from sqlalchemy import and_ from app.models import BudgetPlan #: 执行基准状态(生效版) EFFECTIVE_STATUSES = ("approved", "active") #: UI 展示为「草稿」的状态(`active` = 历史遗留「在用」态) DRAFT_LIKE_STATUSES = ("draft", "active") #: 已下达态(选行优先级最高) _STATUS_ACTIVE = "active" def effective_plan_clause(entity_id: int): """执行基准条件(含账套隔离)—— 单一取数口。 用法:`db.query(BudgetPlan).filter(BudgetPlan.period == p, effective_plan_clause(eid))` """ if entity_id is None: raise ValueError("effective_plan_clause 必须传 entity_id(账套隔离,禁止漏传落默认账套)") return and_( BudgetPlan.entity_id == entity_id, BudgetPlan.status.in_(EFFECTIVE_STATUSES), ) def effective_status_priority(status: str) -> int: """选行优先级:`active`(已下达) 最高,其余生效态次之。""" return 0 if status == _STATUS_ACTIVE else 1 def _sort_key(row): ts = getattr(row, "updated_at", None) try: stamp = ts.timestamp() if ts is not None else 0.0 except (AttributeError, ValueError, OSError): stamp = 0.0 return (effective_status_priority(getattr(row, "status", "") or ""), -stamp) def pick_effective(rows): """同一 `(kpi_id, period)` 的多行 → 取生效行;空输入返回 None。""" if not rows: return None return sorted(rows, key=_sort_key)[0] def effective_value_map(rows): """行列表 → `{(kpi_id, period): budget_value}`(按生效优先级去重)。""" grouped = {} for r in rows: grouped.setdefault((r.kpi_id, r.period), []).append(r) out = {} for key, rows_of_key in grouped.items(): row = pick_effective(rows_of_key) if row is not None: out[key] = row.budget_value return out def is_effective_status(status: str) -> bool: return (status or "") in EFFECTIVE_STATUSES def approval_status_of(raw: str) -> str: """DB status → 前端 `approval_status`(历史兼容:`active` 曾表示在用/草稿态)。""" if not raw: return "draft" if raw in DRAFT_LIKE_STATUSES: return "draft" return raw