fix(cma): entity isolation batch3 (cost abc write endpoints + allocations/stats reads + plan kr_id scope)

- cost.py: POST /abc/activities 按会话账套写入 entity_id;POST /abc/allocate 校验 activity_id 归属本账套(跨账套 404)并透传 entity_id;GET /abc/allocations 加账套过滤
- cost_engine.py: allocate_cost(entity_id=None) 按账套过滤作业中心 + 显式写入 AbcAllocation.entity_id(不传维持原语义)
- action_plans.py: update_plan 的 kr_id 加账套校验(跨账套 404,对齐 create_plan 既有写法);plan_stats join KPIDefinition 按账套过滤(口径与 list_plans 一致)
- tests: 新增 tests/test_cross_entity_isolation_batch3_20260918.py(10 例;改前 7 failed/3 passed → 改后 10 passed)
- 回归: 1184 passed,1 xfailed → 1194 passed,1 xfailed(无新增失败)
This commit is contained in:
Hermes CI Fix
2026-09-18 18:42:38 +08:00
parent 14ab6d211c
commit 9fb9d14a58
4 changed files with 467 additions and 15 deletions
+23 -7
View File
@@ -180,6 +180,12 @@ def create_actual_cost(data: dict, db: Session = Depends(get_db),
# GET /abc/activities 原为全表取(生产 10 行全属 entity2)→ 酣客会话可见博海 ABC 作业中心;
# 现按会话账套过滤(AbcActivity.entity_id)。
# OpenCode 多租户审查 2026-09-07 的「单实体演示数据暂不强制隔离」豁免就此撤销。
# 账套隔离(2026-09-18,project-20260918-entity-isolation-batch3):batch2 同族残留收口
# - POST /abc/activities 原签名无账套依赖、不写归属 → 落模型默认 entity_id=1;现按会话账套写入
# - POST /abc/allocate 原无账套依赖、activity_id 不校验归属、分配记录落默认 1;
# 现校验 activity_id 归属本账套(跨账套 → 404,文案对齐同文件先例)
# 并把 entity_id 透传 cost_engine.allocate_cost 显式写入 AbcAllocation
# - GET /abc/allocations 原查询只按 period/product_code → 现加 entity_id 过滤(读型同族)
# ============================================================
@router.get("/abc/activities")
@@ -193,9 +199,11 @@ def list_abc_activities(db: Session = Depends(get_db), entity_id: int = Depends(
@router.post("/abc/activities")
def create_abc_activity(data: dict, db: Session = Depends(get_db)):
"""创建ABC作业中心"""
def create_abc_activity(data: dict, db: Session = Depends(get_db),
entity_id: int = Depends(get_entity_id)):
"""创建ABC作业中心(账套隔离:按会话账套写入归属)"""
act = AbcActivity(
entity_id=entity_id,
activity_code=data["activity_code"],
activity_name=data["activity_name"],
activity_desc=data.get("activity_desc"),
@@ -211,14 +219,21 @@ def create_abc_activity(data: dict, db: Session = Depends(get_db)):
@router.post("/abc/allocate")
def do_allocate(data: dict, db: Session = Depends(get_db)):
"""执行ABC成本分配"""
def do_allocate(data: dict, db: Session = Depends(get_db),
entity_id: int = Depends(get_entity_id)):
"""执行ABC成本分配(账套隔离:activity_id 须属本账套,跨账套 → 404)"""
act = (db.query(AbcActivity)
.filter(AbcActivity.id == data["activity_id"], AbcActivity.entity_id == entity_id)
.first())
if not act:
raise HTTPException(404, "作业中心不存在")
result = allocate_cost(
activity_id=data["activity_id"],
period=data.get("period", datetime.now().strftime("%Y-%m")),
product_code=data["product_code"],
product_name=data.get("product_name", ""),
driver_consumed=data["driver_consumed"],
entity_id=entity_id,
)
return result
@@ -226,9 +241,10 @@ def do_allocate(data: dict, db: Session = Depends(get_db)):
@router.get("/abc/allocations")
def list_allocations(period: Optional[str] = Query(None),
product_code: Optional[str] = Query(None),
db: Session = Depends(get_db)):
"""查询ABC分配记录"""
query = db.query(AbcAllocation)
db: Session = Depends(get_db),
entity_id: int = Depends(get_entity_id)):
"""查询ABC分配记录(账套隔离:只返回本账套)"""
query = db.query(AbcAllocation).filter(AbcAllocation.entity_id == entity_id)
if period:
query = query.filter(AbcAllocation.period == period)
if product_code: