company.js 15.2 KB
/**
 * Company Routes — 公司管理 API
 *
 * 挂载: /api/company
 */

const express = require('express');
const router = express.Router();
const { CompanyService } = require('../services/company-service');
const { ScheduleService } = require('../services/schedule-service');

const companyService = new CompanyService();
const scheduleService = new ScheduleService();

// ═══════════════════════════════════════
//  Company CRUD
// ═══════════════════════════════════════

// POST /api/company/create
router.post('/create', (req, res) => {
  try {
    const { id, name, description } = req.body;
    if (!id || !name) return res.status(400).json({ error: 'id 和 name 为必填项' });

    // 校验 id 格式 (小写字母、数字、-) 
    if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(id) && id.length < 2) {
      return res.status(400).json({ error: 'id 格式无效 (只允许小写字母、数字、-)' });
    }

    const company = companyService.create({ id, name, description });
    res.json({ success: true, company });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// GET /api/company/list
router.get('/list', (req, res) => {
  res.json(companyService.list());
});

// GET /api/company/templates
router.get('/templates', (req, res) => {
  res.json(companyService.listTemplates());
});

// GET /api/company/:companyId
router.get('/:companyId', (req, res) => {
  const company = companyService.get(req.params.companyId);
  if (!company) return res.status(404).json({ error: '公司不存在' });
  res.json(company);
});

// PUT /api/company/:companyId
router.put('/:companyId', (req, res) => {
  const company = companyService.update(req.params.companyId, req.body);
  if (!company) return res.status(404).json({ error: '公司不存在' });
  res.json({ success: true, company });
});

// POST /api/company/:companyId/shutdown
router.post('/:companyId/shutdown', (req, res) => {
  const company = companyService.shutdown(req.params.companyId);
  if (!company) return res.status(404).json({ error: '公司不存在' });
  res.json({ success: true, company });
});

// ═══════════════════════════════════════
//  Agent 招聘
// ═══════════════════════════════════════

// POST /api/company/:companyId/hire
router.post('/:companyId/hire', async (req, res) => {
  try {
    const { role, type, name, description, customAgent, customSoul, customUser } = req.body;
    if (!role) return res.status(400).json({ error: 'role 为必填项' });

    const result = await companyService.hireWithTraining(req.params.companyId, role, {
      type, name: name || role, description, customAgent, customSoul, customUser,
    });

    // ── P0 FIX: 动态心跳挂载 ──
    // 招聘成功后,立即为这个新 Agent 注册心跳定时器(如果它的 config.json 里 heartbeat.enabled = true)
    try {
      const heartbeatService = require('../services/heartbeat-service');
      const namespacedId = `${req.params.companyId}:${role}`;
      heartbeatService._setupAgentHeartbeat(namespacedId);
      console.log(`[Hire] 💓 已为 ${namespacedId} 动态挂载心跳`);
    } catch (hbErr) {
      console.warn(`[Hire] ⚠️ 心跳挂载跳过: ${hbErr.message}`);
    }

    res.json(result);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// POST /api/company/:companyId/fire
router.post('/:companyId/fire', (req, res) => {
  const { agentId } = req.body;
  if (!agentId) return res.status(400).json({ error: 'agentId 为必填项' });

  const result = companyService.fire(req.params.companyId, agentId);
  if (!result) return res.status(404).json({ error: 'Agent 不存在' });
  res.json(result);
});

// POST /api/company/:companyId/deposit-template — 训练合格后沉淀为模板
router.post('/:companyId/deposit-template', (req, res) => {
  try {
    const { agentId, benchmarkScore } = req.body;
    if (!agentId || benchmarkScore === undefined) {
      return res.status(400).json({ error: 'agentId 和 benchmarkScore 为必填项' });
    }

    const result = companyService.depositTemplate(
      req.params.companyId, agentId, Number(benchmarkScore)
    );
    res.json(result);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// GET /api/company/:companyId/agents
router.get('/:companyId/agents', (req, res) => {
  const agents = companyService.listAgents(req.params.companyId);
  res.json(agents);
});

// ═══════════════════════════════════════
//  Company Goals / OKR (公司级 OKR)
// ═══════════════════════════════════════

const { GoalsService } = require('../services/goals');

// GET /api/company/:companyId/goals
router.get('/:companyId/goals', (req, res) => {
  const goals = new GoalsService(req.params.companyId);
  res.json(goals.list());
});

// POST /api/company/:companyId/goals
router.post('/:companyId/goals', (req, res) => {
  try {
    const { title, description, owner } = req.body;
    if (!title || !owner) {
      return res.status(400).json({ error: 'title 和 owner 为必填项' });
    }
    const goals = new GoalsService(req.params.companyId);
    const goal = goals.create({ title, description, owner });
    res.status(201).json(goal);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// GET /api/company/:companyId/goals/:goalId
router.get('/:companyId/goals/:goalId', (req, res) => {
  const goals = new GoalsService(req.params.companyId);
  const goal = goals.get(req.params.goalId);
  if (!goal) return res.status(404).json({ error: 'Goal not found' });
  res.json(goal);
});

// PUT /api/company/:companyId/goals/:goalId
router.put('/:companyId/goals/:goalId', (req, res) => {
  const goals = new GoalsService(req.params.companyId);
  const result = goals.updateGoal(req.params.goalId, req.body);
  if (!result) return res.status(404).json({ error: 'Goal not found' });
  res.json(result);
});

// POST /api/company/:companyId/goals/:goalId/nodes
router.post('/:companyId/goals/:goalId/nodes', (req, res) => {
  try {
    const { parentNodeId, title, owner, priority } = req.body;
    if (!title || !owner) {
      return res.status(400).json({ error: 'title 和 owner 为必填项' });
    }
    const goals = new GoalsService(req.params.companyId);
    const result = goals.addNode(req.params.goalId, parentNodeId || null, {
      title, owner, priority,
    });
    if (!result) return res.status(404).json({ error: 'Goal not found' });
    res.status(201).json({ nodeId: result.nodeId, node: result.goal.nodes[result.nodeId] });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// PUT /api/company/:companyId/goals/:goalId/nodes/:nodeId
router.put('/:companyId/goals/:goalId/nodes/:nodeId', (req, res) => {
  const goals = new GoalsService(req.params.companyId);
  const result = goals.updateNode(req.params.goalId, req.params.nodeId, req.body);
  if (!result) return res.status(404).json({ error: 'Node not found' });
  res.json({ nodeId: result.nodeId, node: result.node });
});

// GET /api/company/:companyId/goals/:goalId/progress
router.get('/:companyId/goals/:goalId/progress', (req, res) => {
  const goals = new GoalsService(req.params.companyId);
  const progress = goals.getProgress(req.params.goalId);
  if (!progress) return res.status(404).json({ error: 'Goal not found' });
  res.json(progress);
});

// ═══════════════════════════════════════
//  Pipeline / SOP 流水线
// ═══════════════════════════════════════

const { PipelineService } = require('../services/pipeline-service');

// POST /api/company/:companyId/pipelines — 创建流水线
router.post('/:companyId/pipelines', (req, res) => {
  try {
    const pSvc = new PipelineService(req.params.companyId);
    const pipeline = pSvc.createPipeline(req.body);
    res.status(201).json(pipeline);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// GET /api/company/:companyId/pipelines — 列出流水线
router.get('/:companyId/pipelines', (req, res) => {
  const pSvc = new PipelineService(req.params.companyId);
  res.json(pSvc.listPipelines());
});

// GET /api/company/:companyId/pipelines/:pipelineId — 获取流水线定义
router.get('/:companyId/pipelines/:pipelineId', (req, res) => {
  const pSvc = new PipelineService(req.params.companyId);
  const pipeline = pSvc.getPipeline(req.params.pipelineId);
  if (!pipeline) return res.status(404).json({ error: '流水线不存在' });
  res.json(pipeline);
});

// PUT /api/company/:companyId/pipelines/:pipelineId — 更新流水线
router.put('/:companyId/pipelines/:pipelineId', (req, res) => {
  const pSvc = new PipelineService(req.params.companyId);
  const pipeline = pSvc.updatePipeline(req.params.pipelineId, req.body);
  if (!pipeline) return res.status(404).json({ error: '流水线不存在' });
  res.json(pipeline);
});

// POST /api/company/:companyId/pipelines/:pipelineId/run — 启动运行
router.post('/:companyId/pipelines/:pipelineId/run', (req, res) => {
  try {
    const pSvc = new PipelineService(req.params.companyId);
    const run = pSvc.startRun(req.params.pipelineId, req.body);
    res.status(201).json(run);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// GET /api/company/:companyId/pipelines/runs/list — 列出运行记录
router.get('/:companyId/pipelines/runs/list', (req, res) => {
  const pSvc = new PipelineService(req.params.companyId);
  res.json(pSvc.listRuns(req.query.pipelineId));
});

// GET /api/company/:companyId/pipelines/runs/:runId — 获取运行详情
router.get('/:companyId/pipelines/runs/:runId', (req, res) => {
  const pSvc = new PipelineService(req.params.companyId);
  const run = pSvc.getRun(req.params.runId);
  if (!run) return res.status(404).json({ error: '运行记录不存在' });
  res.json(run);
});

// POST /api/company/:companyId/pipelines/runs/:runId/advance — 推进步骤
router.post('/:companyId/pipelines/runs/:runId/advance', (req, res) => {
  try {
    const pSvc = new PipelineService(req.params.companyId);
    const { stepIndex, output, status } = req.body;
    if (stepIndex === undefined) return res.status(400).json({ error: 'stepIndex 为必填项' });
    const run = pSvc.advanceStep(req.params.runId, Number(stepIndex), { output, status });
    res.json(run);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// GET /api/company/:companyId/pipelines/runs/:runId/current — 获取当前任务
router.get('/:companyId/pipelines/runs/:runId/current', (req, res) => {
  const pSvc = new PipelineService(req.params.companyId);
  const task = pSvc.getCurrentTask(req.params.runId);
  if (!task) return res.json({ message: '无就绪任务' });
  res.json(task);
});

// POST /api/company/:companyId/pipelines/runs/:runId/reject — COO 驳回步骤
router.post('/:companyId/pipelines/runs/:runId/reject', (req, res) => {
  try {
    const pSvc = new PipelineService(req.params.companyId);
    const { stepIndex, reason, reviewedBy } = req.body;
    if (stepIndex === undefined) return res.status(400).json({ error: 'stepIndex 为必填项' });
    const run = pSvc.rejectStep(req.params.runId, Number(stepIndex), {
      reason: reason || '',
      reviewedBy: reviewedBy || 'coo',
    });
    res.json(run);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// ═══════════════════════════════════════
//  Schedule (日程管理)
// ═══════════════════════════════════════

// GET /api/company/:companyId/schedule — 获取日程
router.get('/:companyId/schedule', (req, res) => {
  res.json(scheduleService.getSchedule(req.params.companyId));
});

// POST /api/company/:companyId/schedule/slots — 添加日程
router.post('/:companyId/schedule/slots', (req, res) => {
  try {
    const slot = scheduleService.addSlot(req.params.companyId, req.body);
    res.json(slot);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// PUT /api/company/:companyId/schedule/slots/:slotId — 更新日程
router.put('/:companyId/schedule/slots/:slotId', (req, res) => {
  const slot = scheduleService.updateSlot(req.params.companyId, req.params.slotId, req.body);
  if (!slot) return res.status(404).json({ error: '日程不存在' });
  res.json(slot);
});

// DELETE /api/company/:companyId/schedule/slots/:slotId — 删除日程
router.delete('/:companyId/schedule/slots/:slotId', (req, res) => {
  scheduleService.removeSlot(req.params.companyId, req.params.slotId);
  res.json({ success: true });
});

// ═══════════════════════════════════════
//  DataSource (运营数据接入)
// ═══════════════════════════════════════

const { DataSourceService } = require('../services/datasource-service');
const dataSourceService = new DataSourceService();

// GET /api/company/:companyId/data/metrics — 获取运营指标
router.get('/:companyId/data/metrics', async (req, res) => {
  try {
    const { source = 'mock', startDate, endDate } = req.query;
    const sd = startDate || new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10);
    const ed = endDate || new Date().toISOString().slice(0, 10);
    const data = await dataSourceService.fetchMetrics(source, { startDate: sd, endDate: ed });
    res.json(data);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// GET /api/company/:companyId/data/trending — 获取热点趋势
router.get('/:companyId/data/trending', async (req, res) => {
  try {
    const { source = 'mock', platform = 'weibo', count = '10' } = req.query;
    const data = await dataSourceService.fetchTrending(source, { platform, count: parseInt(count, 10) });
    res.json(data);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// GET /api/company/:companyId/data/attribution — 归因分析
router.get('/:companyId/data/attribution', async (req, res) => {
  try {
    const { source = 'mock' } = req.query;
    const now = new Date();
    const currentEnd = now.toISOString().slice(0, 10);
    const currentStart = new Date(now.getTime() - 7 * 86400000).toISOString().slice(0, 10);
    const previousEnd = new Date(now.getTime() - 7 * 86400000).toISOString().slice(0, 10);
    const previousStart = new Date(now.getTime() - 14 * 86400000).toISOString().slice(0, 10);

    const analysis = await dataSourceService.analyzeAttribution(
      source,
      { startDate: currentStart, endDate: currentEnd },
      { startDate: previousStart, endDate: previousEnd }
    );
    res.json(analysis);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

module.exports = router;