fix(alignment): /tree map_id filter via map_objectives.kpis union (v2)
ci/woodpecker/push/main Pipeline failed
ci/woodpecker/push/main Pipeline failed
- add missing 'or_' import (NameError on map_id calls) - union of map_objectives.kpis codes and KPIDefinition.map_id, intersect with entity active KPIs - cross-entity / unknown map_id -> empty tree (no leak) - no map_id -> unchanged behaviour
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
管理员可初始化选择,后续按模式运作"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy import func, or_
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
import json
|
||||
@@ -11,7 +11,7 @@ import json
|
||||
from app.database import get_db
|
||||
from app.auth_middleware import require_auth, require_role, require_auth_401
|
||||
from app.deps import get_entity_id
|
||||
from app.models import KPIDefinition, OperationLog, RolePermission
|
||||
from app.models import KPIDefinition, MapObjective, OperationLog, RolePermission, StrategicMap
|
||||
|
||||
router = APIRouter(prefix="/api/cma/alignment", tags=["KPI目标对齐"],
|
||||
# 用户端点:router 级要求会话 Bearer 鉴权(cma-api-auth-surface-audit-20260914);
|
||||
@@ -146,11 +146,15 @@ def get_alignment_tree(
|
||||
跨实体显式传参(?entity_id=<他人账套>)由 get_entity_id 返回 403。
|
||||
|
||||
地图筛选(2026-10-01 project-20261001-align-tree-map-filter-fullstack):
|
||||
`map_id` 参与**KPI 选择集**过滤——给定 map_id 时只保留
|
||||
`KPIDefinition.map_id == map_id` 的本账套 active KPI;不传时维持改前行为(全量)。
|
||||
跨账套 map_id:KPI 选择集本身已按 entity_id 隔离(且关联写入端点
|
||||
`PUT /kpis/{id}/associate-map` 亦强制 KPI↔地图同账套)⇒ 返回空树,不泄漏他账套 KPI。
|
||||
过滤只作用于 KPI 选择集,三种模式的构建函数(vertical/horizontal/bsc_chain)不变。
|
||||
`map_id` 参与**KPI 选择集**过滤,且只作用于 KPI 选择集——三种模式的构建函数不变。
|
||||
|
||||
过滤取「本账套该地图已关联的 KPI」,关联源按**权威程度**合并两条通道:
|
||||
① `map_objectives.kpis`(**战略地图编辑器**写入的 KPI 编码数组,真源
|
||||
`maps.py::_sync_map_objectives`;现网唯一有内容的通道);
|
||||
② `KPIDefinition.map_id`(KPI 详情页「关联地图」写入;现网 329 行全为 NULL)。
|
||||
两者取并集后与「本账套 `status='active'`」求交。跨账套/不存在的地图 id:先核该地图
|
||||
归属本账套,不属本账套即返回**空树**(不泄漏他账套 KPI)。
|
||||
不传 `map_id`:逐字段维持改前行为(本账套全部 active KPI)。
|
||||
"""
|
||||
# 获取当前账套的对齐模式(B3(a) 拆账套 2026-09-22:只读 alignment_config.<entity_id>)
|
||||
config_perm = _get_alignment_perm(db, entity_id)
|
||||
@@ -163,9 +167,27 @@ def get_alignment_tree(
|
||||
.filter(KPIDefinition.entity_id == entity_id,
|
||||
KPIDefinition.status == "active"))
|
||||
# 地图筛选:只缩小 KPI 选择集,不改动任何构建逻辑
|
||||
empty_by_map = False
|
||||
if map_id is not None:
|
||||
owned = (db.query(StrategicMap.id)
|
||||
.filter(StrategicMap.id == map_id, StrategicMap.entity_id == entity_id)
|
||||
.first())
|
||||
if not owned:
|
||||
empty_by_map = True # 跨账套 / 不存在 ⇒ 空树(不泄漏他账套 KPI)
|
||||
else:
|
||||
codes = set()
|
||||
for (arr,) in (db.query(MapObjective.kpis)
|
||||
.filter(MapObjective.map_id == map_id).all()):
|
||||
if isinstance(arr, list):
|
||||
codes.update(str(c) for c in arr if c)
|
||||
if codes:
|
||||
kpi_query = kpi_query.filter(or_(
|
||||
KPIDefinition.map_id == map_id,
|
||||
KPIDefinition.kpi_code.in_(sorted(codes)),
|
||||
))
|
||||
else:
|
||||
kpi_query = kpi_query.filter(KPIDefinition.map_id == map_id)
|
||||
kpis = kpi_query.order_by(KPIDefinition.kpi_code).all()
|
||||
kpis = [] if empty_by_map else kpi_query.order_by(KPIDefinition.kpi_code).all()
|
||||
kpi_map = {k.id: k for k in kpis}
|
||||
|
||||
# 构建父子关系
|
||||
|
||||
Reference in New Issue
Block a user