При интеграции 1С с IP-телефонией (например, Asterisk, 3CX, Гравител и др.) перед разработчиком часто встает задача: где и как хранить историю звонков? С одной стороны, звонки генерируют огромный поток транзакционной информации — даты, направления, длительность, ответственные менеджеры. С другой стороны, по этим данным необходимо оперативно строить отчеты: рассчитывать общую продолжительность разговоров, количество успешных и пропущенных вызовов за период.
Первое архитектурное решение, которое приходит в голову начинающему разработчику — использовать оборотный Регистр накопления (РН). Ведь именно этот объект конфигурации идеально приспособлен для быстрого получения оборотных показателей. Однако при попытке программной записи данных мы сталкиваемся с жестким ограничением платформы: регистр накопления невозможно использовать без документа-регистратора.
В этой статье мы подробно разберем, почему платформа накладывает такое ограничение, рассмотрим альтернативные варианты проектирования и выберем наиболее оптимальные подходы в зависимости от объемов вашей базы данных.
Для того чтобы понять архитектурный замысел разработчиков платформы «1С:Предприятие», заглянем на уровень физических таблиц СУБД. На уровне базы данных любая таблица движений регистра накопления (как оборотного, так и остатков) содержит обязательное поле Recorder (Регистратор). Это поле входит в состав кластеризованного уникального индекса СУБД.
В концепции 1С регистр накопления не является самостоятельной сущностью. Он предназначен для отражения в учете событий, которые инициируются документами. Документ в данном случае олицетворяет собой транзакцию бизнес-логики. Запись в регистр без ссылки на существующий документ нарушила бы целостность ссылочной структуры базы данных и сделала бы невозможным корректный пересчет итогов при отмене проведения или удалении документов.
Таким образом, обойтись без регистратора в регистре накопления невозможно технически. Но как быть, если данные поступают из внешнего источника (например, через HTTP-сервис или регламентное задание) и дублировать их в виде сотен тысяч документов в системе не хочется?
Рассмотрим детально рабочие альтернативные варианты решения этой задачи.
Если ваша задача — просто сохранять параметры звонков и строить по ним несложные отчеты, лучшим решением будет использование независимого регистра сведений (РС). В отличие от регистра накопления, независимый регистр сведений не привязан к документам. Мы можем писать в него напрямую с помощью менеджера записи или набора записей.
Рассмотрим пример кода для записи входящего звонка в независимый регистр сведений ИсторияЗвонков:
// Создаем менеджер записи для добавления одной строки
НоваяЗапись = РегистрыСведений.ИсторияЗвонков.СоздатьМенеджерЗаписи();
// Заполняем ключевые измерения
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.УникальныйИдентификаторЗвонка = ПараметрыЗвонка.UID; // уникальный ID из телефонии
// Заполняем ресурсы и реквизиты
НоваяЗапись.Абонент = ПараметрыЗвонка.Абонент;
НоваяЗапись.Длительность = ПараметрыЗвонка.Длительность;
НоваяЗапись.Направление = ПараметрыЗвонка.Направление;
НоваяЗапись.Менеджер = ПараметрыЗвонка.Менеджер;
// Записываем данные напрямую в базу без участия документов
НоваяЗапись.Записать(Истина);
Преимущества использования регистра сведений:
Минусы регистра сведений:
Совет по оптимизации регистра сведений: Если вы используете платформу версии 8.3.1 и выше, для наиболее часто используемых в отборах ресурсов (например, Длительность) вы можете установить свойство «Индексировать». Это несколько повысит скорость работы отборов, хотя и не заменит полноценные таблицы итогов регистра накопления.
Что делать, если объемы звонков огромны (сотни тысяч или миллионы записей в месяц), а менеджеры хотят видеть мгновенные отчетики по KPI прямо на рабочем столе? В этом случае использование таблиц итогов регистра накопления становится критически важным.
Чтобы решить проблему с обязательным наличием регистратора, мы можем применить паттерн «Технический регистратор». Суть метода заключается в том, что мы создаем специальный документ (например, ЗагрузкаЗвонков), который не несет смысловой бизнес-нагрузки, а служит лишь «контейнером» для проведения движений по регистру накопления за определенный период.
Важное предостережение: Ни в коем случае не используйте один и тот же единственный документ («документ-пустышку») в качестве регистратора для всех записей за все время! Запись миллионов строк движений под один регистратор гарантированно приведет к критическим блокировкам базы данных при параллельной загрузке, а попытка открыть такой документ в интерфейсе или прочитать его движения намертво повесит сеанс работы пользователя.
Правильный подход — создавать по одному техническому документу на каждую пачку загрузки либо с периодичностью один раз в день. Проанализируем пример кода, реализующий такую схему:
// Функция ищет существующий технический документ за указанную дату или создает новый
Функция ПолучитьТехническийРегистратор(ДатаЗагрузки) Экспорт
НачалоДня = НачалоДня(ДатаЗагрузки);
КонецДня = КонецДня(ДатаЗагрузки);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗагрузкаЗвонков.Ссылка КАК Ссылка
|ИЗ
| Документ.ЗагрузкаЗвонков КАК ЗагрузкаЗвонков
|ГДЕ
| ЗагрузкаЗвонков.Дата МЕЖДУ &НачалоДня И &КонецДня";
Запрос.УстановитьПараметр("НачалоДня", НачалоДня);
Запрос.УстановитьПараметр("КонецДня", КонецДня);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Возврат Выборка.Ссылка;
Иначе
// Создаем новый технический документ для группировки дневных движений
НовыйДокумент = Документы.ЗагрузкаЗвонков.СоздатьДокумент();
НовыйДокумент.Дата = КонецДня;
НовыйДокумент.Комментарий = "Создан автоматически для хранения движений звонков за " + Формат(ДатаЗагрузки, "ДФ=dd.MM.yyyy");
НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
Возврат НовыйДокумент.Ссылка;
КонецЕсли;
КонецФункции
// Процедура пакетной записи звонков в Регистр Накопления
Процедура ЗаписатьЗвонкиВРегистрНакопления(ТаблицаДанныхЗвонков, ДатаЗагрузки) Экспорт
РегистраторСсылка = ПолучитьТехническийРегистратор(ДатаЗагрузки);
// Создаем набор записей регистра накопления с отбором по нашему техническому документу
НаборДвижений = РегистрыНакопления.ЗвонкиОбороты.СоздатьНаборЗаписей();
НаборДвижений.Отбор.Регистратор.Установить(РегистраторСсылка);
// Считываем существующие движения, чтобы не затереть их при добавлении новых в течение дня
НаборДвижений.Прочитать();
Для Каждого СтрокаЗвонка Из ТаблицаДанныхЗвонков Цикл
Движение = НаборДвижений.Добавить();
Движение.Период = СтрокаЗвонка.ДатаВремя;
Движение.Регистратор = РегистраторСсылка;
// Заполняем измерения
Движение.Направление = СтрокаЗвонка.Направление;
Движение.Менеджер = СтрокаЗвонка.Менеджер;
Движение.Абонент = СтрокаЗвонка.Абонент;
// Заполняем ресурсы
Движение.Количество = 1;
Движение.Длительность = СтрокаЗвонка.Продолжительность;
КонецЦикла;
// Записываем набор записей в базу данных
НаборДвижений.Записать();
КонецПроцедуры
Используя данный подход, мы получаем все преимущества оборотного регистра накопления (быстрые отсчеты по таблицам итогов) и при этом не захламляем базу данных миллионами мелких документов-регистраторов, распределяя нагрузку по дням.
Если количество звонков в вашей организации измеряется миллионами записей в неделю, даже классический оборотный регистр накопления начнет снижать производительность при расчете итогов. В этом случае на помощь приходит встроенный механизм агрегатов, разработанный в 1С специально для оптимизации оборотных регистров накопления.
Агрегаты позволяют хранить данные в «свернутом» (агрегированном) виде по заданным разработчиком измерениям. Например, вы можете настроить систему так, чтобы она автоматически хранила предварительно свернутые данные по дням в разрезе только измерений Менеджер и Направление, игнорируя детальное измерение Абонент.
Когда пользователь запустит отчет по эффективности менеджеров, система не будет обращаться к основной таблице движений и даже к стандартной таблице итогов. Она мгновенно возьмет данные из готовой таблицы агрегатов, что ускорит выполнение аналитического запроса в десятки и сотни раз.
Рассмотрим современную альтернативную архитектуру. Очень часто детальная информация о звонках (CDR — Call Detail Records) нужна в 1С исключительно для построения аналитических отчетов и не участвует непосредственно во внутренних транзакциях системы (таких как проведение заказов, начисление зарплаты и т.д.).
Зачем в таком случае вообще нагружать транзакционную базу 1С (файл .1CD или таблицы MS SQL/PostgreSQL) миллионами тяжелых строк? Оптимальным решением будет оставить эти данные в "родной" базе данных IP-телефонии и подключить ее к 1С через механизм Внешних источников данных.
Платформа 1С позволяет напрямую связываться со сторонними базами данных (через ODBC-драйверы) и работать с внешними таблицами в привычном стиле запросов 1С. Вы сможете строить отчеты на СКД, соединяя данные ваших документов 1С с внешней таблицей звонков налету, не совершая физический импорт миллионов строк в основную базу.
Подведем итог. Выбор оптимального архитектурного решения зависит от планируемого объема данных в вашей системе:
| Объем звонков в месяц | Рекомендуемое решение | Почему именно оно? |
|---|---|---|
| До 100 000 записей | Независимый регистр сведений (РС) | Простота реализации, чистота метаданных, отсутствие лишних технических объектов в конфигурации. Быстродействие СУБД будет достаточным без использования итогов. |
| От 100 000 до 1 000 000 записей | Регистр накопления (РН) + Технический регистратор (период — день) | Использование преимуществ таблиц итогов для мгновенного построения аналитических отчетов без перегрузки СУБД полным сканированием таблиц. |
| Более 1 000 000 записей | РН с включенными агрегатами ИЛИ Внешние источники данных | Обеспечение максимальной производительности. Исключение риска раздувания транзакционной базы 1С до критических объемов. |
Проанализировав требования вашего бизнеса к отчетности и спрогнозировав объемы входящих данных, вы сможете спроектировать надежную, быструю и отказоустойчивую систему интеграции телефонии с вашей конфигурацией 1С.