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 fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func, or_
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
@@ -11,7 +11,7 @@ import json
|
|||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
from app.auth_middleware import require_auth, require_role, require_auth_401
|
from app.auth_middleware import require_auth, require_role, require_auth_401
|
||||||
from app.deps import get_entity_id
|
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 = APIRouter(prefix="/api/cma/alignment", tags=["KPI目标对齐"],
|
||||||
# 用户端点:router 级要求会话 Bearer 鉴权(cma-api-auth-surface-audit-20260914);
|
# 用户端点:router 级要求会话 Bearer 鉴权(cma-api-auth-surface-audit-20260914);
|
||||||
@@ -146,11 +146,15 @@ def get_alignment_tree(
|
|||||||
跨实体显式传参(?entity_id=<他人账套>)由 get_entity_id 返回 403。
|
跨实体显式传参(?entity_id=<他人账套>)由 get_entity_id 返回 403。
|
||||||
|
|
||||||
地图筛选(2026-10-01 project-20261001-align-tree-map-filter-fullstack):
|
地图筛选(2026-10-01 project-20261001-align-tree-map-filter-fullstack):
|
||||||
`map_id` 参与**KPI 选择集**过滤——给定 map_id 时只保留
|
`map_id` 参与**KPI 选择集**过滤,且只作用于 KPI 选择集——三种模式的构建函数不变。
|
||||||
`KPIDefinition.map_id == map_id` 的本账套 active KPI;不传时维持改前行为(全量)。
|
|
||||||
跨账套 map_id:KPI 选择集本身已按 entity_id 隔离(且关联写入端点
|
过滤取「本账套该地图已关联的 KPI」,关联源按**权威程度**合并两条通道:
|
||||||
`PUT /kpis/{id}/associate-map` 亦强制 KPI↔地图同账套)⇒ 返回空树,不泄漏他账套 KPI。
|
① `map_objectives.kpis`(**战略地图编辑器**写入的 KPI 编码数组,真源
|
||||||
过滤只作用于 KPI 选择集,三种模式的构建函数(vertical/horizontal/bsc_chain)不变。
|
`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>)
|
# 获取当前账套的对齐模式(B3(a) 拆账套 2026-09-22:只读 alignment_config.<entity_id>)
|
||||||
config_perm = _get_alignment_perm(db, entity_id)
|
config_perm = _get_alignment_perm(db, entity_id)
|
||||||
@@ -163,9 +167,27 @@ def get_alignment_tree(
|
|||||||
.filter(KPIDefinition.entity_id == entity_id,
|
.filter(KPIDefinition.entity_id == entity_id,
|
||||||
KPIDefinition.status == "active"))
|
KPIDefinition.status == "active"))
|
||||||
# 地图筛选:只缩小 KPI 选择集,不改动任何构建逻辑
|
# 地图筛选:只缩小 KPI 选择集,不改动任何构建逻辑
|
||||||
|
empty_by_map = False
|
||||||
if map_id is not None:
|
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)
|
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}
|
kpi_map = {k.id: k for k in kpis}
|
||||||
|
|
||||||
# 构建父子关系
|
# 构建父子关系
|
||||||
|
|||||||
Reference in New Issue
Block a user