feat: OKR模板库P2治理 — owner字段/防重复/校准文档/缺口清单

- 模型+DB: okr_templates.owner (CMA标准库/行业包/用户自定义)
- API: create同名去重(409) + list/get返回owner + apply透传metric_kpi
- 前端: 模板卡片展示owner标签
- 文档: kpi-dictionary-gap-list.md(33条缺口+建议补KPI) + okr-template-calibration.md(酣客/博海校准表)
- 回归: 模板API验证通过
This commit is contained in:
Hermes CI Fix
2026-08-21 16:49:11 +08:00
parent 9ccdd0db63
commit 412a302699
5 changed files with 130 additions and 1 deletions
+14 -1
View File
@@ -68,6 +68,7 @@ def list_okr_templates(
"industry_tag": t.industry_tag,
"preset_krs": t.preset_krs,
"source": t.source,
"owner": t.owner,
"use_count": t.use_count,
"sort_order": t.sort_order,
"is_active": t.is_active,
@@ -93,6 +94,7 @@ def get_okr_template(template_id: int, db: Session = Depends(get_db)):
"industry_tag": t.industry_tag,
"preset_krs": t.preset_krs,
"source": t.source,
"owner": t.owner,
"use_count": t.use_count,
"sort_order": t.sort_order,
"is_active": t.is_active,
@@ -101,7 +103,7 @@ def get_okr_template(template_id: int, db: Session = Depends(get_db)):
@router.post("")
def create_okr_template(data: dict, db: Session = Depends(get_db)):
"""用户自定义 OKR 模板"""
"""用户自定义 OKR 模板(治理: 同名去重 + owner 标记)"""
name = data.get("name", "").strip()
if not name:
raise HTTPException(400, "模板名称不能为空")
@@ -111,6 +113,14 @@ def create_okr_template(data: dict, db: Session = Depends(get_db)):
preset_krs = data.get("preset_krs", [])
if not isinstance(preset_krs, list) or len(preset_krs) == 0:
raise HTTPException(400, "至少需要一个预设KR")
# P2治理: 同名+同维度去重(防止 id=33 式重复模板,2026-08-21
dup = db.query(OKRTemplate).filter(
OKRTemplate.name == name,
OKRTemplate.dimension == dimension,
OKRTemplate.is_active == 1,
).first()
if dup:
raise HTTPException(409, f"已存在同名模板「{name}」(#{dup.id}, source={dup.source}),请改用现有模板或改名")
t = OKRTemplate(
name=name,
@@ -120,6 +130,7 @@ def create_okr_template(data: dict, db: Session = Depends(get_db)):
industry_tag=data.get("industry_tag", "general"),
preset_krs=preset_krs,
source="user",
owner="用户自定义",
sort_order=data.get("sort_order", 0),
)
db.add(t)
@@ -164,6 +175,8 @@ def apply_okr_template(template_id: int, data: dict, db: Session = Depends(get_d
"name": kr.get("name", ""),
"target_value": kr.get("target_value", ""),
"weight": kr.get("weight", 33),
"metric_kpi_id": kr.get("metric_kpi_id"),
"metric_kpi_code": kr.get("metric_kpi_code"),
}
for kr in preset_krs
],