MapCanvasDialogs.vue: - 节点编辑弹窗改为两步交互(模板选择→O+KR编辑) - 第一步:按维度展示对应模板(财务4/客户3/流程4/学习3) - 第二步:编辑O名称+描述+3个KR(名称/目标值/权重) - 14个CMA标准模板硬编码在前端 - 支持添加/删除KR、自定义目标跳过模板 MapCanvas.vue: - 传递 editingLayerKey 到弹窗 - onSaveDialog 适配新 O+KR 数据结构 - onNodeSave 存储 krs 和 template_name 字段 Backend: - OKRTemplate 模型 (okr_templates 表) - okr_templates API: GET(按维度筛选) + POST(用户自定义) + increment use_count - seed_okr_templates.py 迁移脚本(建表+14条种子数据) - 在 main.py 注册新路由
107 lines
3.8 KiB
Python
107 lines
3.8 KiB
Python
"""
|
|
OKR目标管理 API — 季度目标 + 关键结果 + KPI联动
|
|
"""
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import func
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
from app.database import get_db
|
|
from app.auth_middleware import require_role
|
|
from app.models import Objective, ActionPlan
|
|
|
|
router = APIRouter(prefix="/api/cma/okr", tags=["OKR目标管理"],
|
|
dependencies=[Depends(require_role("ceo", "finance", "it"))],
|
|
)
|
|
|
|
|
|
@router.get("")
|
|
def list_objectives(
|
|
quarter: Optional[str] = Query(None, description="筛选季度: 2026Q3"),
|
|
dimension: Optional[str] = Query(None),
|
|
status: Optional[str] = Query(None),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""列出OKR目标"""
|
|
q = db.query(Objective)
|
|
if quarter:
|
|
q = q.filter(Objective.quarter == quarter)
|
|
if dimension:
|
|
q = q.filter(Objective.dimension == dimension)
|
|
if status:
|
|
q = q.filter(Objective.status == status)
|
|
objs = q.order_by(Objective.quarter.desc(), Objective.id).all()
|
|
results = []
|
|
for o in objs:
|
|
krs = db.query(ActionPlan).filter(ActionPlan.objective_id == o.id).all()
|
|
kr_summary = [
|
|
{"id": kr.id, "title": kr.title, "status": kr.status, "progress": kr.progress}
|
|
for kr in krs
|
|
]
|
|
results.append({
|
|
"id": o.id, "title": o.title, "description": o.description,
|
|
"dimension": o.dimension, "quarter": o.quarter,
|
|
"owner": o.owner, "status": o.status, "progress": o.progress,
|
|
"confidence": o.confidence,
|
|
"key_results": kr_summary,
|
|
"kr_count": len(krs),
|
|
"kr_completed": sum(1 for kr in krs if kr.status == "completed"),
|
|
"created_at": o.created_at.isoformat() if o.created_at else None,
|
|
})
|
|
return {"total": len(results), "items": results}
|
|
|
|
|
|
@router.post("")
|
|
def create_objective(
|
|
title: str = Query(...),
|
|
quarter: str = Query(...),
|
|
description: Optional[str] = Query(None),
|
|
dimension: Optional[str] = Query(None),
|
|
owner: Optional[str] = Query(None),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""创建OKR目标"""
|
|
obj = Objective(title=title, quarter=quarter, description=description,
|
|
dimension=dimension, owner=owner)
|
|
db.add(obj)
|
|
db.commit()
|
|
db.refresh(obj)
|
|
return {"ok": True, "id": obj.id, "title": obj.title}
|
|
|
|
|
|
@router.get("/{obj_id}")
|
|
def get_objective(obj_id: int, db: Session = Depends(get_db)):
|
|
"""获取单个OKR详情"""
|
|
obj = db.query(Objective).filter(Objective.id == obj_id).first()
|
|
if not obj:
|
|
raise HTTPException(404, "目标不存在")
|
|
krs = db.query(ActionPlan).filter(ActionPlan.objective_id == obj_id).all()
|
|
return {
|
|
"objective": {
|
|
"id": obj.id, "title": obj.title, "description": obj.description,
|
|
"dimension": obj.dimension, "quarter": obj.quarter,
|
|
"owner": obj.owner, "status": obj.status, "progress": obj.progress,
|
|
"confidence": obj.confidence,
|
|
},
|
|
"key_results": [
|
|
{"id": kr.id, "title": kr.title, "kpi_id": kr.kpi_id,
|
|
"status": kr.status, "progress": kr.progress,
|
|
"due_date": kr.due_date.isoformat() if kr.due_date else None,
|
|
"assignee": kr.assignee}
|
|
for kr in krs
|
|
]
|
|
}
|
|
|
|
|
|
@router.patch("/{obj_id}")
|
|
def update_objective(obj_id: int, db: Session = Depends(get_db)):
|
|
"""更新OKR进度(通过查询ActionPlan自动计算)"""
|
|
obj = db.query(Objective).filter(Objective.id == obj_id).first()
|
|
if not obj:
|
|
raise HTTPException(404, "目标不存在")
|
|
krs = db.query(ActionPlan).filter(ActionPlan.objective_id == obj_id).all()
|
|
if krs:
|
|
obj.progress = sum(kr.progress for kr in krs) // len(krs)
|
|
db.commit()
|
|
return {"ok": True, "id": obj_id, "progress": obj.progress}
|