Рецепт: экспорт сделок в CSV

Стримит каждую сделку CRM, подходящую под фильтр по дате, в CSV-файл через вебхук и FetchListV2.
Мы всё ещё дорабатываем эту страницу. Некоторых данных здесь может не хватать — скоро дополним.

Цель

Выгрузить из портала Bitrix24 каждую сделку CRM, закрытую за период, и записать её в CSV-файл. Память не растёт независимо от числа подходящих сделок — FetchListV2.make() стримит страницы по 50 записей.

Стек и предпосылки

  • Node.js 20+, ESM ("type": "module" в package.json)
  • @bitrix24/b24jssdk@^1.0.0
  • URL входящего вебхука Bitrix24 со scope crm. Задайте его как B24_HOOK в окружении.
pnpm add @bitrix24/b24jssdk
export B24_HOOK="https://your-portal.bitrix24.com/rest/1/xxxxxxxxxxxxxxxx/"

Полный пример

scripts/deals-to-csv.mjs:

import { writeFile } from 'node:fs/promises'
import {
  B24Hook,
  EnumCrmEntityTypeId,
  ParamsFactory,
  Text
} from '@bitrix24/b24jssdk'

const hookUrl = process.env.B24_HOOK
if (!hookUrl) {
  console.error('B24_HOOK is not set')
  process.exit(1)
}

// Закрытые в предыдущем календарном месяце.
const now = new Date()
const from = new Date(now.getFullYear(), now.getMonth() - 1, 1)
const to = new Date(now.getFullYear(), now.getMonth(), 1)

const b24 = B24Hook.fromWebhookUrl(hookUrl, {
  // Меньше нагрузка в секунду, чем у `getDefault()` — хорошо для отчётных задач,
  // работающих рядом с живым пользовательским трафиком на том же портале.
  restrictionParams: ParamsFactory.getBatchProcessing()
})

const generator = b24.actions.v2.fetchList.make({
  method: 'crm.item.list',
  params: {
    entityTypeId: EnumCrmEntityTypeId.deal,
    filter: {
      '>=closedAt': Text.toB24Format(from),
      '<closedAt': Text.toB24Format(to),
      stageSemanticId: 'S' // закрыта-выиграна
    },
    select: ['id', 'title', 'opportunity', 'currencyId', 'closedAt', 'assignedById']
  },
  idKey: 'id',
  customKeyForResult: 'items',
  requestId: 'deals-to-csv'
})

const rows = ['id;title;amount;currency;closedAt;assignedById']
let count = 0
for await (const chunk of generator) {
  for (const deal of chunk) {
    rows.push([
      deal.id,
      JSON.stringify(deal.title ?? ''),
      deal.opportunity ?? '',
      deal.currencyId ?? '',
      deal.closedAt ?? '',
      deal.assignedById ?? ''
    ].join(';'))
  }
  count += chunk.length
  process.stderr.write(`fetched: ${count}\r`)
}

const file = `deals-${from.toISOString().slice(0, 7)}.csv`
await writeFile(file, rows.join('\n'), 'utf8')
console.log(`\nwrote ${count} rows to ${file}`)

Запуск

node scripts/deals-to-csv.mjs
# fetched: 4350
# wrote 4350 rows to deals-2026-04.csv

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

  • B24Hook.fromWebhookUrl() разбирает URL вебхука и даёт готовый экземпляр TypeB24 — инициализация через await не нужна.
  • ParamsFactory.getBatchProcessing() меняет профиль rate limit по умолчанию на настроенный для долгих сканов, чтобы скрипт не конкурировал с живыми UI-запросами за очередь портала.
  • actions.v2.fetchList.make() возвращает AsyncGenerator<T[]>. Каждая итерация for await отдаёт до limit записей (по умолчанию 50) и использует курсорную пагинацию по idKey по возрастанию — SDK сам сшивает страницы.
  • Text.toB24Format(date) преобразует JS-Date в ISO-формат, который Bitrix24 ожидает в фильтрах crm.item.list; передача сырого Date не сработает.

Ограничения

  • crm.item.list возвращает до 50 записей на страницу; это серверный лимит, а не настройка SDK.
  • Генератор не поддерживает пользовательский параметр order — курсорная пагинация требует сортировки по idKey по возрастанию. Указание order в params вызывает runtime-предупреждение и отбрасывается.
  • idKey по умолчанию 'ID' (в верхнем регистре) для v2-endpoint-ов. Методы CRM item возвращают id (в нижнем регистре), поэтому переопределяйте его явно, как в примере.
  • Один экземпляр B24Hook соответствует одному пользователю портала. Если нужные сделки принадлежат другому пользователю, вебхук должен быть создан этим пользователем (или аккаунтом с межпользовательским доступом).
  • Для очень больших периодов, исчерпывающих дневной бюджет вызовов, разбейте диапазон дат на меньшие части и запускайте их последовательно.

См. также