fix(cma): action plan entity_id at the three residual construction sites
alert-to-plan, deviation-push review topic and AI-suggestion-to-plan built ActionPlan(...) without entity_id, so non-e1 sessions wrote rows with the model default 1 and entity_id stayed untrustworthy on those paths. Write the session entity at all three sites, scope the deviation-push topic dedupe by entity (a foreign same-title topic no longer suppresses the session entity's own topic), and require the applied suggestion to belong to the session entity. Adds 7 regression tests (both directions + cross-entity negative controls + auth floor).
This commit is contained in:
@@ -238,6 +238,7 @@ def _apply_action_plan(db: Session, sug: AISuggestion, params: dict, current_use
|
||||
due_date = None
|
||||
|
||||
plan = ActionPlan(
|
||||
entity_id=sug.entity_id, # 2026-09-18 派生单: 写建议归属账套(=会话账套,端点已校验)
|
||||
kpi_id=kpi_id,
|
||||
title=title,
|
||||
description=params.get("description") or sug.content or f"由AI建议 #{sug.id} 生成: {sug.title}",
|
||||
@@ -340,6 +341,7 @@ def apply_suggestion(
|
||||
suggestion_id: int,
|
||||
data: dict,
|
||||
db: Session = Depends(get_db),
|
||||
entity_id: int = Depends(get_entity_id),
|
||||
current_user=Depends(require_auth),
|
||||
):
|
||||
"""应用建议:改KPI目标 / 调预算 / 建行动方案(写库+操作日志留痕)
|
||||
@@ -349,7 +351,10 @@ def apply_suggestion(
|
||||
{"action": "budget_adjust", "period": "2026-09", "budget_value": 100000}
|
||||
{"action": "action_plan", "title": "...", "assignee": "...", "priority": "high", "due_date": "2026-09-30"}
|
||||
"""
|
||||
sug = db.query(AISuggestion).filter(AISuggestion.id == suggestion_id).first()
|
||||
sug = db.query(AISuggestion).filter(
|
||||
AISuggestion.id == suggestion_id,
|
||||
AISuggestion.entity_id == entity_id, # 2026-09-18 派生单: 建议须归属会话账套
|
||||
).first()
|
||||
if not sug:
|
||||
raise HTTPException(404, "建议不存在")
|
||||
if sug.status == "applied":
|
||||
|
||||
@@ -115,6 +115,7 @@ def create_action_plan_from_alert(alert_id: int, data: dict, db: Session = Depen
|
||||
if alert.action_plan_linked_id:
|
||||
raise HTTPException(400, "已关联改善计划")
|
||||
plan = ActionPlan(
|
||||
entity_id=entity_id, # 2026-09-18 派生单: 写会话账套(原落模型默认值 1)
|
||||
alert_id=alert.id,
|
||||
kpi_id=alert.kpi_id,
|
||||
title=f"改善: {alert.alert_message}",
|
||||
|
||||
@@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from app.database import get_db
|
||||
from app.auth_middleware import require_auth, require_role
|
||||
from app.deps import get_entity_id
|
||||
from app.models import StrategicMap, KPIDefinition, KPIValue, KPIAlert, OperationLog, ActionPlan, BudgetPlan
|
||||
import json
|
||||
import logging
|
||||
@@ -22,6 +23,7 @@ router = APIRouter(prefix="/api/cma/deviation-push", tags=["差异反打"],
|
||||
def push_deviation_to_map(
|
||||
data: dict,
|
||||
db: Session = Depends(get_db),
|
||||
entity_id: int = Depends(get_entity_id),
|
||||
current_user=Depends(require_auth),
|
||||
):
|
||||
"""从差异分析回写实际值到战略地图节点
|
||||
@@ -47,9 +49,9 @@ def push_deviation_to_map(
|
||||
if new_value is None:
|
||||
raise HTTPException(400, "缺少 newValue")
|
||||
|
||||
# 1. 查找战略地图
|
||||
# 1. 查找战略地图(2026-09-18 派生单: 地图须归属会话账套,NULL 归属按 e1 兼容)
|
||||
m = db.query(StrategicMap).filter(StrategicMap.id == map_id).first()
|
||||
if not m:
|
||||
if not m or (m.entity_id or 1) != entity_id:
|
||||
raise HTTPException(404, "战略地图不存在")
|
||||
|
||||
# 2. 解析 node_id 格式: "finance-0"
|
||||
@@ -89,7 +91,7 @@ def push_deviation_to_map(
|
||||
|
||||
kpi_code = kpi_codes[0]
|
||||
kpi = db.query(KPIDefinition).filter(KPIDefinition.kpi_code == kpi_code).first()
|
||||
if not kpi:
|
||||
if not kpi or (kpi.entity_id or 1) != entity_id:
|
||||
raise HTTPException(404, f"KPI {kpi_code} 不存在")
|
||||
|
||||
# 3. 更新实际值到 KPIValue 表
|
||||
@@ -147,11 +149,13 @@ def push_deviation_to_map(
|
||||
if create_review_topic:
|
||||
topic_title = f"【差异反打】{kpi.kpi_name}偏差回写 — {target_obj.get('name')}"
|
||||
existing_topic = db.query(ActionPlan).filter(
|
||||
ActionPlan.entity_id == entity_id, # 2026-09-18 派生单: 去重按账套隔离
|
||||
ActionPlan.title == topic_title,
|
||||
ActionPlan.status.in_(["pending", "in_progress"]),
|
||||
).first()
|
||||
if not existing_topic:
|
||||
topic = ActionPlan(
|
||||
entity_id=entity_id, # 2026-09-18 派生单: 写会话账套(原落模型默认值 1)
|
||||
kpi_id=kpi.id,
|
||||
title=topic_title,
|
||||
description=f"由差异分析自动生成:将实际值{new_value}回写至战略地图[{target_dim.get('name')}→{target_obj.get('name')}]节点。差异ID: {deviation_id or 'N/A'}",
|
||||
|
||||
Reference in New Issue
Block a user