/** * OKR API — Objective - Key Result 目标管理路由 * * 旧路径 /api/goals/* 已在 index.js / agent-server.js 注册为向后兼容别名。 * * GET /api/okr → 列出所有目标 * POST /api/okr → 创建新目标 * GET /api/okr/:objectiveId → 获取完整 OKR 树 * PUT /api/okr/:objectiveId → 更新目标元数据 * POST /api/okr/:objectiveId/nodes → 添加节点 * GET /api/okr/:objectiveId/nodes/:nodeId → 获取节点 + 上下文 * PUT /api/okr/:objectiveId/nodes/:nodeId → 更新节点状态 * GET /api/okr/:objectiveId/progress → 获取进度统计 * * 参考: docs/architecture-v5.md 第二章 */ const { Router } = require('express'); const { requireAdmin } = require('../middleware/jwt-auth'); const { OkrService } = require('../services/okr-service'); const router = Router(); const okr = new OkrService(); // 38 号文档 Phase 2:这是独立于 company.js 的遗留全局 OKR 挂载点(`OkrService` 不带 // companyId 实例化,落的是 data/okr/,不属于任何公司)。真正在用的公司级 OKR 是 // company.js 的 /:companyId/okr(挂company.js's router.param('companyId')收口了)—— // 这个独立页面(web/src/views/OkrDashboard.vue,挂载在 /okr)在前端路由/导航栏里 // 找不到任何入口指向它,是个孤儿页面。没有为一个访问不到的页面新建"选公司"的前端交互, // 权衡后按 dream.js 同款处理:不做归属过滤(它本来就没有公司维度可过滤), // 收紧到只有 admin 能看,堵住"能拼 URL 直接看到跨公司聚合数据"这个口子。 // 如果这个页面以后要正式启用,再回来做真正的公司选择 + companyId 归属校验。 router.use(requireAdmin); // ── 列出所有目标 ── router.get('/', (req, res) => { res.json(okr.list()); }); // ── 创建新目标 ── router.post('/', (req, res) => { try { const { title, description, owner } = req.body; if (!title || !owner) { return res.status(400).json({ error: 'title and owner are required' }); } const objective = okr.create({ title, description, owner }); res.status(201).json(objective); } catch (err) { res.status(500).json({ error: err.message }); } }); // ── 获取完整 OKR 树 ── router.get('/:objectiveId', (req, res) => { const objective = okr.get(req.params.objectiveId); if (!objective) return res.status(404).json({ error: 'Objective not found' }); res.json(objective); }); // ── 更新目标元数据 ── router.put('/:objectiveId', (req, res) => { const result = okr.updateObjective(req.params.objectiveId, req.body); if (!result) return res.status(404).json({ error: 'Objective not found' }); res.json(result); }); // ── 添加节点 ── router.post('/:objectiveId/nodes', (req, res) => { try { const { parentNodeId, title, owner, priority } = req.body; if (!title || !owner) { return res.status(400).json({ error: 'title and owner are required' }); } const result = okr.addNode(req.params.objectiveId, parentNodeId || null, { title, owner, priority, }); if (!result) return res.status(404).json({ error: 'Objective not found' }); res.status(201).json({ nodeId: result.nodeId, node: result.objective.nodes[result.nodeId] }); } catch (err) { res.status(500).json({ error: err.message }); } }); // ── 获取节点 + 上下文 ── router.get('/:objectiveId/nodes/:nodeId', (req, res) => { const node = okr.getNode(req.params.objectiveId, req.params.nodeId); if (!node) return res.status(404).json({ error: 'Node not found' }); res.json(node); }); // ── 更新节点状态 ── router.put('/:objectiveId/nodes/:nodeId', (req, res) => { const result = okr.updateNode(req.params.objectiveId, req.params.nodeId, req.body); if (!result) return res.status(404).json({ error: 'Node not found' }); res.json({ nodeId: result.nodeId, node: result.node }); }); // ── 获取进度 ── router.get('/:objectiveId/progress', (req, res) => { const progress = okr.getProgress(req.params.objectiveId); if (!progress) return res.status(404).json({ error: 'Objective not found' }); res.json(progress); }); module.exports = router;