fix(cma): scope alert list and alert locate to alert row entity_id; attach cash-balance suffix candidates to forecast baseline

- alerts list: filter KPIAlert.entity_id (was KPIDefinition.entity_id via join) -> entity2 225->223, rows 589/590 no longer leak into entity2 workspace

- resolve/process/escalate/create-action-plan: same row-level scope (4 sites)

- get_current_cash_balance: pass KPI_CODE_SUFFIXES[cash_balance] -> entity2 hits BH_CASH_BALANCE(434), entity6 hits DEMO_CASH_BALANCE(468)

no data changes; no SystemConfig fallback change; no period ordering change
This commit is contained in:
Hermes CI Fix
2026-09-19 08:06:17 +08:00
parent 557771a803
commit 4225332aaf
2 changed files with 27 additions and 14 deletions
+23 -13
View File
@@ -16,8 +16,10 @@ router = APIRouter(prefix="/api/cma/alerts", tags=["预警"],
@router.get("")
def list_alerts(status: str = None, page: int = Query(1, ge=1), db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
# 账套隔离: join kpi_definitions 按企业过滤 (2026-08-23 P1b)
query = db.query(KPIAlert).join(KPIDefinition, KPIDefinition.id == KPIAlert.kpi_id).filter(KPIDefinition.entity_id == entity_id)
# 账套隔离(2026-09-19 dama-20260919 C1):按**预警行自身** entity_id 过滤
# 修复前按 KPI 归属企业过滤(2026-08-23 P1b)→ KPI 属他账套而预警行属本账套的行反向泄漏
# (实测 entity 2 口径 225 → 223,泄漏 id=589/590)
query = db.query(KPIAlert).filter(KPIAlert.entity_id == entity_id)
if status:
query = query.filter(KPIAlert.status == status)
total = query.count()
@@ -26,8 +28,10 @@ def list_alerts(status: str = None, page: int = Query(1, ge=1), db: Session = De
@router.post("/{alert_id}/resolve")
def resolve_alert(alert_id: int, data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
alert = db.query(KPIAlert).join(KPIDefinition, KPIDefinition.id == KPIAlert.kpi_id).filter(
KPIAlert.id == alert_id, KPIDefinition.entity_id == entity_id).first()
# 账套隔离(2026-09-19 dama-20260919 C2):按预警行自身 entity_id 定位
# 修复前按 KPI 归属企业过滤 → KPI 属他账套的行可被本账套端点操作(跨账套泄漏/越权)
alert = db.query(KPIAlert).filter(
KPIAlert.id == alert_id, KPIAlert.entity_id == entity_id).first()
if not alert:
raise HTTPException(404, "预警不存在")
alert.status = "resolved"
@@ -46,9 +50,11 @@ def resolve_alert(alert_id: int, data: dict, db: Session = Depends(get_db), enti
@router.post("/{alert_id}/process")
def process_alert(alert_id: int, data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
"""标记预警为处理中(账套隔离: join kpi_definitions 按企业过滤, 2026-09-18)"""
alert = db.query(KPIAlert).join(KPIDefinition, KPIDefinition.id == KPIAlert.kpi_id).filter(
KPIAlert.id == alert_id, KPIDefinition.entity_id == entity_id).first()
"""标记预警为处理中(账套隔离: 按预警行自身 entity_id, 2026-09-19)"""
# 账套隔离(2026-09-19 dama-20260919 C2):按预警行自身 entity_id 定位
# 修复前按 KPI 归属企业过滤 → KPI 属他账套的行可被本账套端点操作(跨账套泄漏/越权)
alert = db.query(KPIAlert).filter(
KPIAlert.id == alert_id, KPIAlert.entity_id == entity_id).first()
if not alert:
raise HTTPException(404, "预警不存在")
if alert.status == "resolved":
@@ -65,9 +71,11 @@ def process_alert(alert_id: int, data: dict, db: Session = Depends(get_db), enti
@router.post("/{alert_id}/escalate")
def escalate_alert(alert_id: int, data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
"""升级预警级别(账套隔离: join kpi_definitions 按企业过滤, 2026-09-18)"""
alert = db.query(KPIAlert).join(KPIDefinition, KPIDefinition.id == KPIAlert.kpi_id).filter(
KPIAlert.id == alert_id, KPIDefinition.entity_id == entity_id).first()
"""升级预警级别(账套隔离: 按预警行自身 entity_id, 2026-09-19)"""
# 账套隔离(2026-09-19 dama-20260919 C2):按预警行自身 entity_id 定位
# 修复前按 KPI 归属企业过滤 → KPI 属他账套的行可被本账套端点操作(跨账套泄漏/越权)
alert = db.query(KPIAlert).filter(
KPIAlert.id == alert_id, KPIAlert.entity_id == entity_id).first()
if not alert:
raise HTTPException(404, "预警不存在")
assignee = data.get("assignee")
@@ -107,9 +115,11 @@ def check_alert_timeout(db: Session = Depends(get_db), entity_id: int = Depends(
@router.post("/{alert_id}/create-action-plan")
def create_action_plan_from_alert(alert_id: int, data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
"""从预警创建改善行动计划(账套隔离: join kpi_definitions 按企业过滤, 2026-09-18)"""
alert = db.query(KPIAlert).join(KPIDefinition, KPIDefinition.id == KPIAlert.kpi_id).filter(
KPIAlert.id == alert_id, KPIDefinition.entity_id == entity_id).first()
"""从预警创建改善行动计划(账套隔离: 按预警行自身 entity_id, 2026-09-19)"""
# 账套隔离(2026-09-19 dama-20260919 C2):按预警行自身 entity_id 定位
# 修复前按 KPI 归属企业过滤 → KPI 属他账套的行可被本账套端点操作(跨账套泄漏/越权)
alert = db.query(KPIAlert).filter(
KPIAlert.id == alert_id, KPIAlert.entity_id == entity_id).first()
if not alert:
raise HTTPException(404, "预警不存在")
if alert.action_plan_linked_id:
+4 -1
View File
@@ -224,7 +224,10 @@ def calc_trend(values: list) -> float:
def get_current_cash_balance(db: Session, entity_id: int) -> Optional[float]:
"""获取当前现金余额(万元)— 优先级:KPI实际值 > SystemConfig > None"""
from app.models import SystemConfig, KPIValue
kpi = find_kpi(db, entity_id, KPI_CODE_CANDIDATES["cash_balance"])
# C3(2026-09-19 dama-20260919):补传实体前缀后缀候选(与 F2 的应收/应付路径一致)
# 修复前漏传 → 博海/演示企业一条候选都不命中 → 回退 SystemConfig.cash.current_balance(全库唯一键 -9.3)
kpi = find_kpi(db, entity_id, KPI_CODE_CANDIDATES["cash_balance"],
KPI_CODE_SUFFIXES["cash_balance"])
if kpi:
latest_cash = db.query(KPIValue).filter(
KPIValue.kpi_id == kpi.id,