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:
@@ -186,6 +186,15 @@ class TestBudgetVersions:
|
||||
json={"kpi_id": kpi.id, "period": "2026-06", "budget_value": 10000.0},
|
||||
)
|
||||
|
||||
# 状态机口径归一(2026-10-03):审批只接受 submitted 行 → 先提交
|
||||
sub = client.post(
|
||||
f"{self.BASE}/versions/submit",
|
||||
headers=auth_header(token),
|
||||
json={"version": "v1.0"},
|
||||
)
|
||||
assert sub.status_code == 200
|
||||
assert sub.json()["count"] == 1
|
||||
|
||||
resp = client.post(
|
||||
f"{self.BASE}/versions/approve",
|
||||
headers=auth_header(token),
|
||||
@@ -205,6 +214,14 @@ class TestBudgetVersions:
|
||||
json={"kpi_id": kpi.id, "period": "2026-06", "budget_value": 10000.0},
|
||||
)
|
||||
|
||||
# 状态机口径归一(2026-10-03):审批只接受 submitted 行 → 先提交
|
||||
sub = client.post(
|
||||
f"{self.BASE}/versions/submit",
|
||||
headers=auth_header(token),
|
||||
json={"version": "v1.0"},
|
||||
)
|
||||
assert sub.status_code == 200
|
||||
|
||||
resp = client.post(
|
||||
f"{self.BASE}/versions/approve",
|
||||
headers=auth_header(token),
|
||||
|
||||
Reference in New Issue
Block a user