В процессе разработки в среде «1С:Предприятие 8» часто возникает задача автоматизации процессов, привязанных к конкретным объектам базы данных. Представьте ситуацию: у вас есть справочник контрагентов, торговых точек, касс или внешних интеграций, и для каждого элемента этого справочника необходимо выполнять определенное действие (например, обмен данными) по своему уникальному расписанию. Напрямую привязать одно регламентное задание из метаданных к множеству разных расписаний невозможно, поскольку объект метаданных РегламентноеЗадание имеет только одно системное расписание.
В этой статье мы подробно разберем, как решить эту задачу. Мы проанализируем три основных архитектурных подхода, рассмотрим их преимущества и недостатки, а также приведем конкретные примеры программного кода.
Это наиболее гибкий, производительный и масштабируемый вариант, который не перегружает СУБД и планировщик платформы «1С:Предприятие». Суть метода заключается в создании одного общего регламентного задания (диспетчера), которое запускается достаточно часто (например, раз в 1–5 минут) и инициирует выполнение задач по элементам справочника, у которых подошло время запуска.
Давайте разберем по шагам, как реализовать этот паттерн.
Шаг 1. Хранение расписания в справочнике
Объект платформы РасписаниеРегламентногоЗадания нельзя напрямую сохранить в реквизит базы данных стандартного типа. Чтобы обойти это ограничение, мы будем сериализовать расписание в XML-строку и хранить его в реквизите справочника с типом Строка (неограниченной длины) или ХранилищеЗначения. Также нам понадобятся реквизиты Использование (тип Булево) и индексированный реквизит ДатаСледующегоЗапуска (тип Дата).
Рассмотрим, как программно сохранить расписание, настроенное пользователем через стандартный диалог:
&НаКлиенте
Процедура НастроитьРасписание(Команда)
// Открываем диалог настройки расписания
Диалог = Новый ДиалогРасписанияРегламентногоЗадания(ПолучитьРасписаниеИзСтроки(Объект.РасписаниеXML));
Оповещение = Новый ОписаниеОповещения("ПослеНастройкиРасписания", ЭтотОбъект);
Диалог.Показать(Оповещение);
КонецПроцедуры
&НаКлиенте
Процедура ПослеНастройкиРасписания(Расписание, ДополнительныеПараметры) Экспорт
Если Расписание <> Неопределено Тогда
Объект.РасписаниеXML = ПолучитьСтрокуXMLИзРасписания(Расписание);
// Рассчитываем дату следующего запуска прямо при записи расписания
Объект.ДатаСледующегоЗапуска = ПолучитьДатуСледующегоЗапускаНаСервере(Расписание);
КонецЕсли;
КонецПроцедуры
Для преобразования расписания в XML и обратно на сервере используются следующие функции:
&НаСервереБезКонтекста
Функция ПолучитьСтрокуXMLИзРасписания(Расписание)
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
ЗаписатьXML(ЗаписьXML, Расписание);
Возврат ЗаписьXML.Закрыть();
КонецФункции
&НаСервереБезКонтекста
Функция ПолучитьРасписаниеИзСтроки(СтрокаXML)
Если ПустаяСтрока(СтрокаXML) Тогда
Возврат Новый РасписаниеРегламентногоЗадания;
КонецЕсли;
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаXML);
Возврат ПрочитатьXML(ЧтениеXML, Тип("РасписаниеРегламентногоЗадания"));
КонецФункции
Шаг 2. Расчет даты следующего запуска
Чтобы диспетчер работал мгновенно, нам не нужно перебирать все элементы справочника и проверять их сложные расписания в цикле. Мы будем использовать метод РасписаниеРегламентногоЗадания.ПолучитьДатуСледующегоЗапуска(). При записи элемента справочника или после очередного его выполнения мы рассчитываем точную дату и записываем ее в индексированный реквизит ДатаСледующегоЗапуска.
&НаСервереБезКонтекста
Функция ПолучитьДатуСледующегоЗапускаНаСервере(Расписание)
Возврат Расписание.ПолучитьДатуСледующегоЗапуска(ТекущаяДатаСеанса());
КонецФункции
Шаг 3. Логика работы Диспетчера
Создаем общее регламентное задание, которое обращается к общему серверному модулю. Процедура-диспетчер выбирает только те элементы, время запуска которых уже наступило, запускает их выполнение в фоновом режиме, а затем обновляет дату следующего запуска для каждого отработавшего элемента:
Процедура ВыполнитьДиспетчеризациюЗаданий() Экспорт
ТекущееВремя = ТекущаяДатаСеанса();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| МойСправочник.Ссылка КАК Ссылка,
| МойСправочник.РасписаниеXML КАК РасписаниеXML
|ИЗ
| Справочник.МойСправочник КАК МойСправочник
|ГДЕ
| МойСправочник.Использование = ИСТИНА
| И МойСправочник.ДатаСледующегоЗапуска <= &ТекущееВремя";
Запрос.УстановитьПараметр("ТекущееВремя", ТекущееВремя);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Запускаем обработку элемента асинхронно через фоновое задание
ПараметрыЗапуска = Новый Массив;
ПараметрыЗапуска.Добавить(Выборка.Ссылка);
ФоновыеЗадания.Выполнить(
"ОбщийМодульСервер.ВыполнитьДействиеПоЭлементу",
ПараметрыЗапуска,
Строка(Выборка.Ссылка.УникальныйИдентификатор()),
"Запуск обработки для: " + Строка(Выборка.Ссылка)
);
// Рассчитываем новое время следующего запуска
Расписание = ПолучитьРасписаниеИзСтроки(Выборка.РасписаниеXML);
СледующийЗапуск = Расписание.ПолучитьДатуСледующегоЗапуска(ТекущееВремя + 1); // Сдвиг на секунду вперед
// Обновляем данные в справочнике
ОбъектЭлемента = Выборка.Ссылка.ПолучитьОбъект();
ОбъектЭлемента.ДатаСледующегоЗапуска = СледующийЗапуск;
ОбъектЭлемента.Записать();
КонецЦикла;
КонецПроцедуры
Этот подход гарантирует высокую скорость работы, так как запрос к СУБД происходит по индексированному полю даты, а сами тяжелые процессы выполняются параллельно в фоновых заданиях, не блокируя работу основного диспетчера.
Платформа «1С:Предприятие» позволяет программно создавать полноценные регламентные задания в режиме «1С:Предприятие» (в пользовательском режиме).
Рассмотрим этот вариант. При таком подходе каждому элементу справочника будет соответствовать свое собственное уникальное регламентное задание в общем списке системных регламентных заданий.
Реализация механизма:
ИдентификаторРегламентногоЗадания (тип УникальныйИдентификатор или Строка).Посмотрим на пример кода, выполняемый в модуле объекта справочника при записи:
Процедура ПередЗаписью(Отказ)
// Проверим, заполнено ли расписание
Если ПустаяСтрока(РасписаниеXML) Тогда
Возврат;
КонецЕсли;
// Попытаемся найти существующее регламентное задание
РегЗадание = Неопределено;
Если ЗначениеЗаполнено(ИдентификаторРегламентногоЗадания) Тогда
ИД = Новый УникальныйИдентификатор(ИдентификаторРегламентногоЗадания);
РегЗадание = РегламентныеЗадания.НайтиПоУникальномуИдентификатору(ИД);
КонецЕсли;
// Если задание не найдено, создаем новое
Если РегЗадание = Неопределено Тогда
РегЗадание = РегламентныеЗадания.СоздатьРегламентноеЗадание("ВыполнитьДействиеПоЭлементуСправочника");
ИдентификаторРегламентногоЗадания = Строка(РегЗадание.УникальныйИдентификатор);
КонецЕсли;
// Настраиваем параметры регламентного задания
РегЗадание.Наименование = "Обработка элемента: " + Наименование;
РегЗадание.Использование = Использование;
// Передаем ссылку на текущий элемент в массив параметров задания
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(Ссылка);
РегЗадание.Параметры = ПараметрыЗадания;
// Устанавливаем расписание
РегЗадание.Расписание = ПолучитьРасписаниеИзСтроки(РасписаниеXML);
// Сохраняем регламентное задание в системном списке
РегЗадание.Записать();
КонецПроцедуры
Не забудьте обработать процедуру удаления элемента справочника: при удалении элемента связанное регламентное задание также необходимо программно удалить из системы с помощью метода РегламентноеЗадание.Удалить().
Важное ограничение метода: Данный вариант отлично подходит, если количество элементов справочника невелико (до 50–100 элементов). Если в вашем справочнике будут тысячи элементов, создание тысяч системных регламентных заданий создаст колоссальную нагрузку на планировщик платформы и СУБД, что может привести к зависанию сервера 1С.
Если ваша конфигурация разработана на базе БСП (Библиотеки Стандартных Подсистем), например, «1С:ERP», «1С:Комплексная автоматизация», «Управление торговлей» или «Бухгалтерия предприятия», то в вашем распоряжении уже есть готовый мощный промышленный механизм — подсистема «Очередь заданий».
Этот механизм специально спроектирован для работы в нагруженных системах, а также полностью совместим с работой в облачной модели сервиса (например, в «1С:Фреш»), где создание обычных регламентных заданий напрямую запрещено из соображений безопасности.
В БСП за управление такими задачами отвечает справочник ШаблоныЗаданийОчереди и регистр сведений ОчередьЗаданий. Чтобы задействовать этот механизм, выполните следующие шаги:
ОчередьЗаданий вашей конфигурации.ОбщегоНазначенияКлиентСервер.РасписаниеВСтруктуру() и обратную функцию СтруктураВРасписание(). Это позволяет легко упаковывать сложные объекты расписания в простые структуры данных для безопасной передачи между сервером и клиентом.ОчередьЗаданий.ДобавитьЗадание(), передавая туда имя метода, параметры и структуру расписания. БСП сама возьмет на себя заботу о своевременном запуске, логировании, обработке ошибок и перезапуске в случае аварийного завершения фонового процесса.Мы проанализировали три основных подхода к реализации индивидуальных расписаний для элементов справочника. Подведем итоги, какой метод выбрать для вашего проекта: