feat: P0/P1/P2全部功能 — 四层泳道/视角切换/KPI看板/预警/差异反打/预算/知识面板/回顾会/情景预测/Excel导入/角色权限
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
"""KPI模板库 API — 管理会计OS
|
||||
支持系统预置模板 + 用户自定义模板
|
||||
从模板实例化创建KPI时,复制模板快照到kpi_definitions"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
from app.database import get_db
|
||||
from app.auth_middleware import require_role
|
||||
from app.models import KPITemplate, KPIDefinition, OperationLog
|
||||
|
||||
router = APIRouter(prefix="/api/cma/templates", tags=["KPI模板库"],
|
||||
dependencies=[Depends(require_role("ceo", "finance", "business", "it"))],
|
||||
)
|
||||
|
||||
WRITE_ROLES = Depends(require_role("ceo", "finance", "it"))
|
||||
|
||||
|
||||
def template_to_dict(t):
|
||||
return {c.name: getattr(t, c.name) for c in t.__table__.columns}
|
||||
|
||||
|
||||
@router.get("")
|
||||
def list_templates(
|
||||
dimension: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
keyword: Optional[str] = None,
|
||||
is_system: Optional[int] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取模板列表,支持按维度/类别/关键字筛选"""
|
||||
query = db.query(KPITemplate)
|
||||
if dimension:
|
||||
query = query.filter(KPITemplate.dimension == dimension)
|
||||
if category:
|
||||
query = query.filter(KPITemplate.category == category)
|
||||
if keyword:
|
||||
query = query.filter(KPITemplate.kpi_name.contains(keyword))
|
||||
if is_system is not None:
|
||||
query = query.filter(KPITemplate.is_system == is_system)
|
||||
templates = query.order_by(KPITemplate.is_system.desc(), KPITemplate.kpi_code).all()
|
||||
return {"total": len(templates), "data": [template_to_dict(t) for t in templates]}
|
||||
|
||||
|
||||
@router.get("/{template_id}")
|
||||
def get_template(template_id: int, db: Session = Depends(get_db)):
|
||||
t = db.query(KPITemplate).filter(KPITemplate.id == template_id).first()
|
||||
if not t:
|
||||
raise HTTPException(404, "模板不存在")
|
||||
return template_to_dict(t)
|
||||
|
||||
|
||||
@router.post("")
|
||||
def create_template(data: dict, db: Session = Depends(get_db), user=WRITE_ROLES):
|
||||
"""用户创建自定义模板"""
|
||||
existing = db.query(KPITemplate).filter(KPITemplate.kpi_code == data.get("kpi_code", "")).first()
|
||||
if existing:
|
||||
raise HTTPException(400, f"模板编码 {data['kpi_code']} 已存在")
|
||||
t = KPITemplate(
|
||||
kpi_code=data.get("kpi_code"),
|
||||
kpi_name=data.get("kpi_name"),
|
||||
dimension=data.get("dimension"),
|
||||
category=data.get("category"),
|
||||
formula=data.get("formula"),
|
||||
formula_desc=data.get("formula_desc"),
|
||||
unit=data.get("unit", "%"),
|
||||
target_value=data.get("target_value"),
|
||||
description=data.get("description"),
|
||||
is_system=0, # 用户创建的永远不是系统模板
|
||||
usage_count=0,
|
||||
)
|
||||
db.add(t)
|
||||
db.commit()
|
||||
db.refresh(t)
|
||||
_log(db, 1, "create", "template", t.id, {"kpi_code": t.kpi_code, "kpi_name": t.kpi_name})
|
||||
return template_to_dict(t)
|
||||
|
||||
|
||||
@router.put("/{template_id}")
|
||||
def update_template(template_id: int, data: dict, db: Session = Depends(get_db), user=WRITE_ROLES):
|
||||
"""修改自定义模板(系统预置不可修改)"""
|
||||
t = db.query(KPITemplate).filter(KPITemplate.id == template_id).first()
|
||||
if not t:
|
||||
raise HTTPException(404, "模板不存在")
|
||||
if t.is_system:
|
||||
raise HTTPException(403, "系统预置模板不可修改")
|
||||
for k, v in data.items():
|
||||
if hasattr(t, k) and v is not None:
|
||||
setattr(t, k, v)
|
||||
db.commit()
|
||||
return template_to_dict(t)
|
||||
|
||||
|
||||
@router.delete("/{template_id}")
|
||||
def delete_template(template_id: int, db: Session = Depends(get_db), user=WRITE_ROLES):
|
||||
"""删除自定义模板(系统预置不可删除)"""
|
||||
t = db.query(KPITemplate).filter(KPITemplate.id == template_id).first()
|
||||
if not t:
|
||||
raise HTTPException(404, "模板不存在")
|
||||
if t.is_system:
|
||||
raise HTTPException(403, "系统预置模板不可删除")
|
||||
db.delete(t)
|
||||
db.commit()
|
||||
return {"message": "模板已删除"}
|
||||
|
||||
|
||||
@router.post("/{template_id}/instantiate")
|
||||
def instantiate_template(template_id: int, data: dict, db: Session = Depends(get_db), user=WRITE_ROLES):
|
||||
"""从模板实例化创建KPI,复制模板快照到kpi_definitions"""
|
||||
t = db.query(KPITemplate).filter(KPITemplate.id == template_id).first()
|
||||
if not t:
|
||||
raise HTTPException(404, "模板不存在")
|
||||
|
||||
kpi_code = data.get("kpi_code", t.kpi_code)
|
||||
kpi_name = data.get("kpi_name", t.kpi_name)
|
||||
|
||||
# 检查编码唯一性
|
||||
existing = db.query(KPIDefinition).filter(KPIDefinition.kpi_code == kpi_code).first()
|
||||
if existing:
|
||||
raise HTTPException(400, f"KPI编码 {kpi_code} 已存在,请修改")
|
||||
|
||||
kpi = KPIDefinition(
|
||||
template_id=t.id,
|
||||
is_system=0, # 从模板实例化的KPI不是系统预置
|
||||
kpi_code=kpi_code,
|
||||
kpi_name=kpi_name,
|
||||
dimension=data.get("dimension", t.dimension),
|
||||
category=data.get("category", t.category),
|
||||
formula=data.get("formula", t.formula),
|
||||
formula_desc=data.get("formula_desc", t.formula_desc),
|
||||
unit=data.get("unit", t.unit or "%"),
|
||||
target_value=data.get("target_value", t.target_value),
|
||||
objective=data.get("objective"),
|
||||
data_source_type=data.get("data_source_type", "manual"),
|
||||
frequency=data.get("frequency", "monthly"),
|
||||
responsible_dept=data.get("responsible_dept"),
|
||||
responsible_user=data.get("responsible_user"),
|
||||
status="active",
|
||||
)
|
||||
db.add(kpi)
|
||||
db.commit()
|
||||
db.refresh(kpi)
|
||||
|
||||
# 更新模板使用计数
|
||||
t.usage_count = (t.usage_count or 0) + 1
|
||||
db.commit()
|
||||
|
||||
_log(db, 1, "create", "kpi", kpi.id, {"from_template": template_id, "kpi_code": kpi.kpi_code})
|
||||
return {c.name: getattr(kpi, c.name) for c in kpi.__table__.columns}
|
||||
|
||||
|
||||
def _log(db, user_id, action, target_type, target_id, detail):
|
||||
import json
|
||||
log = OperationLog(user_id=user_id, action=action, target_type=target_type,
|
||||
target_id=target_id, detail=json.dumps(detail, ensure_ascii=False) if detail else None)
|
||||
db.add(log)
|
||||
db.commit()
|
||||
Reference in New Issue
Block a user