"""KPI派生规则 API — 管理会计OS (P2-② 2026-08-28) 派生规则配置(budget_derivation_rules):apply-method 派生KPI时优先读规则, percentage_of → base_kpi实际值×rate;incremental → 上月×(1+rate);无规则fallback默认比例。 """ from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from typing import Optional from app.database import get_db from app.deps import get_entity_id from app.auth_middleware import require_auth, require_role from app.models import BudgetDerivationRule, KPIDefinition router = APIRouter( prefix="/api/cma/budget", tags=["派生规则"], dependencies=[Depends(require_role("ceo", "finance", "it"))], ) @router.get("/derivation-rules") def list_derivation_rules( kpi_id: Optional[int] = Query(None), status: Optional[str] = Query(None), db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id), ): """派生规则列表(按 entity_id 隔离)""" query = db.query(BudgetDerivationRule).filter(BudgetDerivationRule.entity_id == entity_id) if kpi_id: query = query.filter(BudgetDerivationRule.kpi_id == kpi_id) if status: query = query.filter(BudgetDerivationRule.status == status) rows = query.order_by(BudgetDerivationRule.id.desc()).all() all_kpi_ids = {r.kpi_id for r in rows} | {r.base_kpi_id for r in rows if r.base_kpi_id} kpis = {k.id: k for k in db.query(KPIDefinition).filter(KPIDefinition.id.in_(all_kpi_ids)).all()} if all_kpi_ids else {} result = [] for r in rows: kpi = kpis.get(r.kpi_id) base = kpis.get(r.base_kpi_id) if r.base_kpi_id else None result.append({ "id": r.id, "kpi_id": r.kpi_id, "kpi_code": kpi.kpi_code if kpi else "", "kpi_name": kpi.kpi_name if kpi else "", "rule_type": r.rule_type, "base_kpi_id": r.base_kpi_id, "base_kpi_code": base.kpi_code if base else "", "base_kpi_name": base.kpi_name if base else "", "params": r.params, "formula_text": r.formula_text, "status": r.status, "created_at": r.created_at.isoformat() if r.created_at else None, }) return {"data": result, "total": len(result)} @router.post("/derivation-rules") def create_derivation_rule( data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id), current_user=Depends(require_auth), ): """新建派生规则(同KPI同类型唯一)""" kpi_id = data.get("kpi_id") rule_type = data.get("rule_type") if not kpi_id or rule_type not in ("incremental", "percentage_of", "formula"): raise HTTPException(400, "需要 kpi_id 且 rule_type ∈ incremental/percentage_of/formula") kpi = db.query(KPIDefinition).filter( KPIDefinition.id == kpi_id, KPIDefinition.entity_id == entity_id, ).first() if not kpi: raise HTTPException(404, "KPI不存在") existing = db.query(BudgetDerivationRule).filter( BudgetDerivationRule.entity_id == entity_id, BudgetDerivationRule.kpi_id == kpi_id, BudgetDerivationRule.rule_type == rule_type, ).first() if existing: raise HTTPException(400, f"KPI({kpi_id})已存在 {rule_type} 规则") row = BudgetDerivationRule( entity_id=entity_id, kpi_id=kpi_id, rule_type=rule_type, base_kpi_id=data.get("base_kpi_id"), params=data.get("params"), formula_text=data.get("formula_text"), status=data.get("status", "active"), ) db.add(row) db.commit() db.refresh(row) return {"message": "派生规则已创建", "id": row.id} @router.put("/derivation-rules/{rule_id}") def update_derivation_rule( rule_id: int, data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id), current_user=Depends(require_auth), ): """更新派生规则""" row = db.query(BudgetDerivationRule).filter( BudgetDerivationRule.id == rule_id, BudgetDerivationRule.entity_id == entity_id, ).first() if not row: raise HTTPException(404, "规则不存在") for field in ("rule_type", "base_kpi_id", "params", "formula_text", "status"): if field in data: setattr(row, field, data[field]) db.commit() return {"message": "派生规则已更新", "id": row.id} @router.delete("/derivation-rules/{rule_id}") def delete_derivation_rule( rule_id: int, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id), current_user=Depends(require_auth), ): """删除派生规则""" row = db.query(BudgetDerivationRule).filter( BudgetDerivationRule.id == rule_id, BudgetDerivationRule.entity_id == entity_id, ).first() if not row: raise HTTPException(404, "规则不存在") db.delete(row) db.commit() return {"message": "派生规则已删除"}