- OpenCode执行模式: 补 test_maps.py 5个测试(create_objective/snapshot隔离/entity注入/跨企业) - OpenCode诚实标注: list_objectives 后端漏entity过滤 → 修复(跨企业返回空) - 记忆层闭环: Hermes验证(pytest 536 passed) + 技能已更新(opencode-independent-review 双角色) - 执行层试点验证: OpenCode能写代码+发现后端缺口, 最小闭环跑通
98 lines
3.9 KiB
Python
98 lines
3.9 KiB
Python
"""战略地图目标 API"""
|
||
from fastapi import APIRouter, Depends, HTTPException
|
||
from sqlalchemy.orm import Session
|
||
from app.database import get_db
|
||
from app.deps import get_entity_id
|
||
from app.auth_middleware import require_role
|
||
from app.models import MapObjective, StrategicMap, KPIDefinition
|
||
|
||
router = APIRouter(prefix="/api/cma/maps", tags=["战略地图目标"],
|
||
dependencies=[Depends(require_role("ceo", "finance"))],
|
||
)
|
||
|
||
|
||
@router.get("/{map_id}/objectives")
|
||
def list_objectives(map_id: int, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
|
||
"""获取某地图下的所有目标(账套隔离: 跨企业返回空, OpenCode执行层发现)"""
|
||
m = db.query(StrategicMap).filter(StrategicMap.id == map_id, StrategicMap.entity_id == entity_id).first()
|
||
if not m:
|
||
return {"data": []}
|
||
objs = db.query(MapObjective).filter(
|
||
MapObjective.map_id == map_id
|
||
).order_by(MapObjective.sort_order).all()
|
||
return {"data": [_obj_to_dict(o) for o in objs]}
|
||
|
||
|
||
@router.post("/{map_id}/objectives")
|
||
def create_objective(map_id: int, data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
|
||
"""新增目标"""
|
||
m = db.query(StrategicMap).filter(StrategicMap.id == map_id, StrategicMap.entity_id == entity_id).first()
|
||
if not m:
|
||
raise HTTPException(404, "战略地图不存在")
|
||
obj = MapObjective(
|
||
map_id=map_id,
|
||
dimension_key=data["dimension_key"],
|
||
name=data["name"],
|
||
description=data.get("description"),
|
||
icon=data.get("icon", "target"),
|
||
sort_order=data.get("sort_order", 0),
|
||
)
|
||
db.add(obj)
|
||
db.commit()
|
||
db.refresh(obj)
|
||
return _obj_to_dict(obj)
|
||
|
||
|
||
@router.put("/{map_id}/objectives/{obj_id}")
|
||
def update_objective(map_id: int, obj_id: int, data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
|
||
"""修改目标(账套隔离 + 字段白名单, OpenCode审查#8)"""
|
||
m = db.query(StrategicMap).filter(StrategicMap.id == map_id, StrategicMap.entity_id == entity_id).first()
|
||
if not m:
|
||
raise HTTPException(404, "战略地图不存在")
|
||
obj = db.query(MapObjective).filter(
|
||
MapObjective.id == obj_id, MapObjective.map_id == map_id
|
||
).first()
|
||
if not obj:
|
||
raise HTTPException(404, "目标不存在")
|
||
ALLOWED_OBJ_FIELDS = {"name", "description", "icon", "sort_order", "kpis", "dimension_key"}
|
||
for k, v in data.items():
|
||
if k in ALLOWED_OBJ_FIELDS and hasattr(obj, k) and v is not None:
|
||
setattr(obj, k, v)
|
||
db.commit()
|
||
return _obj_to_dict(obj)
|
||
|
||
|
||
@router.delete("/{map_id}/objectives/{obj_id}")
|
||
def delete_objective(map_id: int, obj_id: int, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
|
||
"""删除目标(账套隔离)"""
|
||
m = db.query(StrategicMap).filter(StrategicMap.id == map_id, StrategicMap.entity_id == entity_id).first()
|
||
if not m:
|
||
raise HTTPException(404, "战略地图不存在")
|
||
obj = db.query(MapObjective).filter(
|
||
MapObjective.id == obj_id, MapObjective.map_id == map_id
|
||
).first()
|
||
if not obj:
|
||
raise HTTPException(404, "目标不存在")
|
||
db.delete(obj)
|
||
db.commit()
|
||
return {"message": "已删除"}
|
||
|
||
|
||
@router.put("/{map_id}/objectives/sort")
|
||
def sort_objectives(map_id: int, data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
|
||
"""批量排序: {"ids": [3, 1, 2]}(账套隔离)"""
|
||
m = db.query(StrategicMap).filter(StrategicMap.id == map_id, StrategicMap.entity_id == entity_id).first()
|
||
if not m:
|
||
raise HTTPException(404, "战略地图不存在")
|
||
ids = data.get("ids", [])
|
||
for idx, obj_id in enumerate(ids):
|
||
db.query(MapObjective).filter(
|
||
MapObjective.id == obj_id, MapObjective.map_id == map_id
|
||
).update({"sort_order": idx})
|
||
db.commit()
|
||
return {"message": "排序已更新"}
|
||
|
||
|
||
def _obj_to_dict(o):
|
||
return {c.name: getattr(o, c.name) for c in o.__table__.columns}
|