#Автоматизация задач

Сложность: medium | Скоупы: task, crm | Стек: Node.js

#Что делаем

Создаём сервис, который следит за изменением стадий сделок в CRM и автоматически создаёт задачи при переходе сделки на определённый этап. Например, когда сделка переходит в стадию «В работе», создаётся задача для менеджера с описанием и дедлайном. Это устраняет человеческий фактор и стандартизирует бизнес-процессы.

#Необходимо

  • API-ключ Вайбкод с правами task, crm
  • Node.js 18+

#Полный код

javascript
// task-automation.js
// Автоматическое создание задач при смене стадии сделки

const API_KEY = process.env.VIBE_API_KEY;
const BASE_URL = process.env.VIBE_BASE_URL; // например https://vibecode.bitrix24.tech

const HEADERS = {
  'X-Api-Key': API_KEY,
  'Content-Type': 'application/json',
};

// Конфигурация: какие задачи создавать при переходе на стадию
const STAGE_TASKS = {
  EXECUTING: {
    title: 'Подготовить документы и начать исполнение',
    description: 'Сделка перешла в работу. Необходимо подготовить все документы и начать исполнение обязательств.',
    deadlineDays: 5,
    priority: 2, // высокий
  },
  PREPAYMENT_INVOICE: {
    title: 'Выставить счёт и проконтролировать оплату',
    description: 'Необходимо выставить счёт на предоплату и отследить поступление средств.',
    deadlineDays: 3,
    priority: 2,
  },
  FINAL_INVOICE: {
    title: 'Финальная сверка и закрытие',
    description: 'Финальный этап сделки. Подготовить закрывающие документы.',
    deadlineDays: 7,
    priority: 1,
  },
};

// Хранилище последних известных стадий сделок
const dealStages = new Map();

// Получаем активные сделки через Entity API
async function fetchActiveDeals() {
  const response = await fetch(
    `${BASE_URL}/v1/deals/search`,
    {
      method: 'POST',
      headers: HEADERS,
      body: JSON.stringify({
        // Фильтруем незакрытые сделки (исключаем WON и LOSE стадии)
        // Примечание: стадии с множественными воронками имеют C-префикс (C2:WON, C4:LOSE)
        filter: {
          '!stageId': 'WON',
        },
        select: ['id', 'title', 'stageId', 'assignedById'],
      }),
    }
  );

  const { data } = await response.json();
  return data || [];
}

// Создаём задачу через Entity API
async function createTask(deal, taskConfig) {
  const deadline = new Date();
  deadline.setDate(deadline.getDate() + taskConfig.deadlineDays);

  const response = await fetch(`${BASE_URL}/v1/tasks`, {
    method: 'POST',
    headers: HEADERS,
    body: JSON.stringify({
      title: `${taskConfig.title} — ${deal.title}`,
      description: [
        taskConfig.description,
        '',
        `Сделка: ${deal.title} (ID: ${deal.id})`,
      ].join('\n'),
      responsibleId: deal.assignedById,
      priority: taskConfig.priority,
      deadline: deadline.toISOString(),
    }),
  });

  const { data } = await response.json();
  console.log(`   📝 Задача создана: #${data.id} — ${taskConfig.title}`);
  return data.id;
}

// Основной цикл: проверка изменений стадий
async function checkStageChanges() {
  const timestamp = new Date().toISOString();
  console.log(`\n[${timestamp}] Проверяем изменения стадий...`);

  const deals = await fetchActiveDeals();

  for (const deal of deals) {
    const dealId = deal.id;
    const currentStage = deal.stageId;
    const previousStage = dealStages.get(dealId);

    // Обновляем известную стадию
    dealStages.set(dealId, currentStage);

    // Если стадия изменилась и для новой стадии есть конфигурация задачи
    if (previousStage && previousStage !== currentStage) {
      console.log(`\n🔄 Сделка #${dealId} "${deal.title}": ${previousStage} → ${currentStage}`);

      const taskConfig = STAGE_TASKS[currentStage];
      if (taskConfig) {
        await createTask(deal, taskConfig);
      } else {
        console.log(`   Для стадии ${currentStage} задача не настроена`);
      }
    }
  }

  // Очищаем закрытые сделки из хранилища
  const activeIds = new Set(deals.map((d) => d.id));
  for (const [id] of dealStages) {
    if (!activeIds.has(id)) {
      dealStages.delete(id);
    }
  }
}

// Запуск с интервалом
async function start() {
  console.log('✅ Сервис автоматизации задач запущен');
  console.log(`   Отслеживаемые стадии: ${Object.keys(STAGE_TASKS).join(', ')}`);

  // Первый запуск — заполняем начальное состояние
  console.log('\nЗагружаем текущее состояние сделок...');
  const deals = await fetchActiveDeals();
  for (const deal of deals) {
    dealStages.set(deal.id, deal.stageId);
  }
  console.log(`Загружено ${deals.length} активных сделок\n`);

  // Запускаем проверку каждые 60 секунд
  setInterval(checkStageChanges, 60_000);
}

start().catch(console.error);

#Как это работает

  1. При запуске сервис загружает все открытые сделки через Entity API (POST /v1/deals/search) и запоминает их текущие стадии.
  2. Каждые 60 секунд происходит повторный опрос сделок.
  3. Если у сделки изменилась стадия, сервис проверяет конфигурацию STAGE_TASKS — определены ли задачи для новой стадии.
  4. При совпадении создаётся задача через Entity API (POST /v1/tasks) с заголовком, описанием и дедлайном.
  5. Закрытые сделки автоматически удаляются из отслеживания для экономии памяти.

Примечание: Чек-листы задач пока создаются через интерфейс Битрикс24 или прямой REST API. Метод task.checklistitem.add не имеет entity-обёртки в Вайбкод.

#Что можно улучшить

  • Использовать webhooks вместо polling для мгновенной реакции на изменения
  • Добавить конфигурацию через YAML/JSON файл, чтобы менеджеры могли настраивать правила без кода
  • Реализовать дедупликацию: не создавать задачу, если аналогичная уже существует для этой сделки
  • Добавить логирование в файл и уведомления об ошибках в Telegram/Slack