diff --git a/backend/app/api/alerts.py b/backend/app/api/alerts.py index f852c7d0..0ac16e31 100644 --- a/backend/app/api/alerts.py +++ b/backend/app/api/alerts.py @@ -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: diff --git a/backend/app/utils/cash_forecast_engine.py b/backend/app/utils/cash_forecast_engine.py index c0d27a9a..1d6c01a5 100644 --- a/backend/app/utils/cash_forecast_engine.py +++ b/backend/app/utils/cash_forecast_engine.py @@ -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,