feat(budget): effective-version caliber + process-order tabs + 4 missing rings
Task: CMA-20261003 (budget module governance). Local commit only; push is
gated on release approval (see report).
Backend
- app/utils/budget_status.py (new): single source for the effective-version
caliber. EFFECTIVE_STATUSES = approved + active; effective_plan_clause()
enforces entity isolation; pick_effective() priority active > approved >
updated_at desc. Fixes: approved budgets disappeared from the execution
surface because the three queries only read status='active'.
- budget.py: /comparison, /comparison/kpi/{id}, /deviation-check now read the
effective surface; versions list gains is_effective / can_release fields;
submit/approve are fail-closed (draft|active -> submitted -> approved).
- deviation_engine.get_budget_for_kpi: effective surface + deterministic pick.
- 4 missing rings, zero DDL, appended to the same router:
POST /versions/release (approved -> active, idempotent by operation_logs),
POST /adjustments (derive a new draft version, base rows untouched),
GET /control/status (effective budget vs actual -> ok/warn/over),
GET /assessment (achieve rate + A/B/C/D grade).
Audit rows written through app.utils.oplog.log_op with entity_id.
Frontend
- BudgetManagement.vue: top-level tabs reordered to the process order
(strategy, method, driver, input, decompose, versions, release, execution,
deviation, adjust, assess, rolling). Pane name values unchanged, so old
bookmarks keep working; routes unchanged.
- Cash classify rules / unclassified queue moved to /cash-plan
(components/CashClassifyPanel.vue); budget actual-value collection moved to
/data-manage (components/BudgetCollectPanel.vue). Mount points only: no
change to cash_plans data, classify logic, collection logic or kpi_values.
- api/index.ts: 4 ring endpoints exposed.
Docs / tests
- docs/budget-module-governance-design.md: phase-0 design doc (status machine,
tab order, domain boundaries, 4-ring design, cleanup write pack scope).
- tests/test_budget_status_machine.py (new): effective surface, version
guards, release idempotency, adjustment, control and assessment.
- tests/test_budget.py: existing cases adapted to the fail-closed contract.
(cherry picked from commit 4267fc4d25ee45c5675a541af9777be96fe9f9c2)
This commit is contained in:
@@ -13,6 +13,7 @@ from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from app.database import get_session_local
|
||||
from app.utils.budget_status import EFFECTIVE_STATUSES, pick_effective
|
||||
from app.models import KPIDefinition, KPIValue, KPIAlert, BudgetPlan, SystemConfig
|
||||
# 裁定 R1/R2/R3(2026-09-19 财务口径):demo 账套排除 / 期间窗口 / 规整月期间
|
||||
from app.utils.alert_entity_scope import (
|
||||
@@ -93,14 +94,15 @@ def calc_deviation(actual: float, budget: float) -> dict:
|
||||
|
||||
def get_budget_for_kpi(db, kpi_id: int, period: str, version: str = None) -> Optional[float]:
|
||||
"""获取指定KPI在指定期间的预算值"""
|
||||
# 生效版口径(2026-10-03 状态机归一):approved(已批准) ∪ active(已下达)
|
||||
query = db.query(BudgetPlan).filter(
|
||||
BudgetPlan.kpi_id == kpi_id,
|
||||
BudgetPlan.period == period,
|
||||
BudgetPlan.status == "active",
|
||||
BudgetPlan.status.in_(EFFECTIVE_STATUSES),
|
||||
)
|
||||
if version:
|
||||
query = query.filter(BudgetPlan.version == version)
|
||||
plan = query.order_by(BudgetPlan.updated_at.desc()).first()
|
||||
plan = pick_effective(query.all()) # active > approved > updated_at desc
|
||||
return plan.budget_value if plan else None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user