fix(cma): entity isolation batch4 (cash plan writes, budget ownership, read leaks, okr template)
P0-A cash.py: scope api_update_plan / api_delete_plan / api_complete_plan /
api_register_payment to session entity (cross-entity id -> 404, aligning with
the file's existing get_entity_id endpoints).
P0-B budget.py: write entity_id on BudgetPlan creation + require the KPI to
belong to the session entity; scope /comparison, /comparison/kpi/{id} and
/deviation-report reads to the session entity.
P1 notifications.py: explicit field whitelist (name/config/enabled) on
update_channel to close mass-assignment.
P1 read leaks scoped to session entity: bot_kpis list, data_quality check/stats
(KPI calibre only; quality-log table has no entity_id column), kpis causality
chain + associate-map (KPI and map ownership), dashboard my-kpis,
customer_dashboard list, kpi_governance audit, budget_generate candidates,
driver_budget history (+ entity_id on its write path), expenses stats
(+ entity_id on its write path).
P2 okr_templates.py: write entity_id on the StrategicMap created by apply.
tests: tests/test_cross_entity_isolation_batch4_20260921.py (30 cases;
RED before fix = 22 failed, GREEN after = 30 passed; full suite 1302 passed /
1 xfailed / 0 failed).
This commit is contained in:
@@ -10,7 +10,7 @@ import json
|
||||
import logging
|
||||
|
||||
from app.database import get_db
|
||||
from app.deps import assert_entity_scope
|
||||
from app.deps import assert_entity_scope, get_entity_id
|
||||
from app.auth_middleware import require_auth, require_role
|
||||
from app.models import KPIDefinition, KPIValue, KpiDataQualityLog, OperationLog
|
||||
from app.api.kpis import kpi_to_dict
|
||||
@@ -36,9 +36,12 @@ def _log_to_dict(log):
|
||||
# ============================================================
|
||||
|
||||
@router.get("/check")
|
||||
def run_quality_check(db: Session = Depends(get_db)):
|
||||
"""扫描全部KPI,生成数据质量报告"""
|
||||
kpis = db.query(KPIDefinition).filter(KPIDefinition.status == "active").all()
|
||||
def run_quality_check(db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
|
||||
"""扫描本账套KPI,生成数据质量报告(账套隔离 2026-09-21)"""
|
||||
kpis = db.query(KPIDefinition).filter(
|
||||
KPIDefinition.status == "active",
|
||||
KPIDefinition.entity_id == entity_id,
|
||||
).all()
|
||||
issues = []
|
||||
current_period = datetime.now().strftime("%Y-%m")
|
||||
|
||||
@@ -228,9 +231,12 @@ def delete_quality_log(log_id: int, db: Session = Depends(get_db), user=WRITE_RO
|
||||
# ============================================================
|
||||
|
||||
@router.get("/stats")
|
||||
def quality_stats(db: Session = Depends(get_db)):
|
||||
"""数据质量统计"""
|
||||
total_kpis = db.query(KPIDefinition).filter(KPIDefinition.status == "active").count()
|
||||
def quality_stats(db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
|
||||
"""数据质量统计(账套隔离 2026-09-21:KPI 口径按会话账套;质量日志表无 entity_id 列,见回报)"""
|
||||
total_kpis = db.query(KPIDefinition).filter(
|
||||
KPIDefinition.status == "active",
|
||||
KPIDefinition.entity_id == entity_id,
|
||||
).count()
|
||||
total_logs = db.query(KpiDataQualityLog).count()
|
||||
open_logs = db.query(KpiDataQualityLog).filter(KpiDataQualityLog.status == "open").count()
|
||||
|
||||
@@ -256,7 +262,10 @@ def quality_stats(db: Session = Depends(get_db)):
|
||||
|
||||
# ── 数据审计看板统计 ──
|
||||
# KPI完整度评分
|
||||
all_kpis = db.query(KPIDefinition).filter(KPIDefinition.status == "active").all()
|
||||
all_kpis = db.query(KPIDefinition).filter(
|
||||
KPIDefinition.status == "active",
|
||||
KPIDefinition.entity_id == entity_id, # 账套隔离 2026-09-21
|
||||
).all()
|
||||
total = len(all_kpis)
|
||||
complete_kpis = 0
|
||||
missing_metadata_count = 0
|
||||
|
||||
Reference in New Issue
Block a user