В процессе автоматизации бизнес-процессов в 1С часто возникает задача регулярной отправки отчетов или выполнения обменов данными в строго определенные даты. Классический пример — отправка данных по ключевым показателям эффективности (КПЭ) во внешнюю систему (например, в SAP) строго 26-го числа каждого месяца. Однако бизнес-логика накладывает важное ограничение: если 26-е число выпадает на выходной или праздничный день, запуск процесса должен быть перенесен на первый последующий рабочий день (27-е или 28-е число).
В этой статье мы подробно разберем, как правильно реализовать подобный алгоритм с использованием регламентных заданий, как связать логику с производственным календарем и как избежать распространенных ошибок начинающих разработчиков, связанных с контекстом выполнения серверного кода.
Для реализации этой задачи нам потребуется задействовать несколько механизмов платформы 1С:Предприятие 8:
Главная архитектурная сложность заключается в том, что регламентное задание выполняется в фоновом режиме на сервере 1С. У него нет визуального интерфейса, нет открытых форм и нет интерактивного пользователя. Весь код должен быть полностью автономным и не зависеть от клиентского контекста.
Прежде чем писать код, подготовим для него место в конфигурации. Создадим новый общий модуль, в котором будет располагаться наша процедура запуска.
Рассмотрим ключевые свойства общего модуля, которые необходимо настроить в палитре свойств:
Для того чтобы система понимала, является ли конкретная дата выходным днем, мы будем обращаться к производственному календарю. В типовых конфигурациях 1С для этого используется регистр сведений РегламентированныйПроизводственныйКалендарь (в современных конфигурациях на БСП он может называться ДанныеПроизводственногоКалендаря).
Напишем функцию, которая принимает проверяемую дату, делает запрос к регистру и возвращает Истина, если день является выходным или праздничным, и Ложь, если день рабочий или предпраздничный.
Функция ЭтоВыходнойДень(ПроверяемаяДата) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Календарь.ВидДня
|ИЗ
| РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК Календарь
|ГДЕ
| Календарь.ДатаКалендаря = &ПроверяемаяДата
| И Календарь.ВидДня В (
| ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий),
| ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Предпраздничный)
| )";
Запрос.УстановитьПараметр("ПроверяемаяДата", НачалоДня(ПроверяемаяДата));
Результат = Запрос.Выполнить();
// Если запрос вернул пустой результат, значит, день НЕ рабочий и НЕ предпраздничный.
// Соответственно, это выходной или праздничный день.
Возврат Результат.Пустой();
КонецФункции
Обратите внимание на использование оператора В и перечисления видов дней. Мы объединили проверку на Рабочий и Предпраздничный дни через скобки, чтобы корректно обрабатывать сокращенные рабочие дни перед государственными праздниками.
На форумах часто предлагают решать задачу «в лоб», прописывая жесткие условия для каждого возможного дня смещения (например, отдельно проверять 26, 27 и 28 числа). Этот подход крайне хрупок. Если праздничные дни продлятся дольше (например, новогодние каникулы или майские праздники), жестко закодированные условия не сработают.
Мы применим более элегантный и универсальный алгоритм с использованием цикла Пока. Система будет брать базовую дату (26-е число) и сдвигать ее вперед на один день до тех пор, пока не встретит рабочий день.
Проанализируем этот алгоритм на примере кода:
// Определяем базовую дату — 26 число текущего месяца
БазоваяДата = Дата(Год(ТекущаяДата()), Месяц(ТекущаяДата()), 26);
ФактическаяДатаЗапуска = БазоваяДата;
// Сдвигаем дату вперед на сутки (86400 секунд), пока она является выходной
Пока ЭтоВыходнойДень(ФактическаяДатаЗапуска) Цикл
ФактическаяДатаЗапуска = ФактическаяДатаЗапуска + 86400;
КонецЦикла;
// Если сегодняшняя дата не совпадает с вычисленной рабочей датой запуска — выходим
Если НачалоДня(ТекущаяДата()) <> НачалоДня(ФактическаяДатаЗапуска) Тогда
Возврат;
КонецЕсли;
Этот алгоритм полностью автоматизирует расчет. Если 26-е число — это рабочий день, цикл завершится сразу, и задание выполнится 26-го числа. Если 26-е выпадет на субботу, цикл прибавит день и проверит воскресенье 27-го (тоже выходной), затем прибавит еще день и остановится на понедельнике 28-го. В этот день условие совпадения выполнится, и код обмена запустится.
Начинающие разработчики часто пытаются перенести логику интерактивной формы в регламентное задание «как есть». В кодовой базе форм часто фигурируют переменные вроде СтатФорма или Организация, которые заполняются пользователем на клиенте. При переносе такого кода на сервер возникает ошибка компиляции: Переменная не определена (СтатФорма).
Поскольку регламентное задание работает автономно, оно должно получать параметры из информационной базы. Для этого мы можем использовать:
Разберем готовый вариант процедуры регламентного задания, где параметры обмена считываются динамически из базы данных, а все ключевые события фиксируются в системном журнале:
Процедура ВыполнитьОбменСSAP() Экспорт
// 1. Проверяем, наступил ли день отправки с учетом выходных
БазоваяДата = Дата(Год(ТекущаяДата()), Месяц(ТекущаяДата()), 26);
ФактическаяДата = БазоваяДата;
Пока ЭтоВыходнойДень(ФактическаяДата) Цикл
ФактическаяДата = ФактическаяДата + 86400;
КонецЦикла;
Если НачалоДня(ТекущаяДата()) <> НачалоДня(ФактическаяДата) Тогда
Возврат; // Сегодня не день обмена, тихо выходим
КонецЕсли;
// 2. Получаем параметры обмена, которые раньше выбирались на форме.
// В данном примере мы считываем форму из настроек или находим по коду.
СтатФорма = ПолучитьНастройкуИДФормы();
Если Не ЗначениеЗаполнено(СтатФорма) Тогда
ЗаписьЖурналаРегистрации(
"ОбменSAP.Ошибка",
УровеньЖурналаРегистрации.Ошибка,
,
,
"Не удалось запустить обмен: не заполнен идентификатор статистической формы!"
);
Возврат;
КонецЕсли;
Организация = Справочники.Организации.НайтиПоКоду("000000001");
// 3. Формируем структуру параметров и перезаполняем отчетность
ПараметрыОтчета = Новый Структура;
ПараметрыОтчета.Вставить("ИДФормы", СтатФорма);
ПараметрыОтчета.Вставить("Организация", Организация);
ПараметрыОтчета.Вставить("НачалоПериода", НачалоМесяца(ТекущаяДата()));
ПараметрыОтчета.Вставить("КонецПериода", КонецМесяца(ТекущаяДата()));
Попытка
// Вызываем логику перезаполнения данных в информационной базе
РегистрыСведений.тн_СтатистическаяОтчетностьДляSAP.ПерезаполнитьОтчетность(ПараметрыОтчета);
КодФормы = РегистрыСведений.тн_СтатистическаяОтчетностьДляSAP.ПолучитьКодФормыПоИДФормы(СтатФорма);
Если ЗначениеЗаполнено(КодФормы) Тогда
// Непосредственная передача подготовленных данных во внешнюю систему SAP
тн_ОбменSAP.ПередатьВSAP(КодФормы, НачалоМесяца(ТекущаяДата()), КонецМесяца(ТекущаяДата()));
ЗаписьЖурналаРегистрации(
"ОбменSAP.Успех",
УровеньЖурналаРегистрации.Информация,
,
,
"Обмен успешно выполнен для формы: " + Сокращенно(КодФормы)
);
КонецЕсли;
Исключение
// Обязательно перехватываем любые ошибки, чтобы задание не "вешало" рабочий процесс сервера
ЗаписьЖурналаРегистрации(
"ОбменSAP.Ошибка",
УровеньЖурналаРегистрации.Ошибка,
,
,
"Произошла ошибка при выполнении обмена: " + ОписаниеОшибки()
);
КонецПопытки;
КонецПроцедуры
Функция ПолучитьНастройкуИДФормы()
// Вспомогательная функция получения настройки.
// Здесь может быть запрос к константе или регистру настроек.
Возврат Справочники.тн_СтатистическиеФормы.НайтиПоРеквизиту("КодДляSAP", "KPI_FORM_01");
КонецФункции
После написания кода перейдем к настройке метаданных в дереве конфигурации:
ОбщийМодуль.ИмяМодуля.ВыполнитьОбменСSAP.Важное правило настройки расписания: поскольку логика сдвига дат полностью реализована программно внутри нашей процедуры, расписание в Конфигураторе нужно настроить на запуск Каждый день, один раз в день (например, в 23:00).
Если настроить запуск только на 26-е число, то в месяце, где 26-е окажется субботой, задание запустится в субботу, увидит выходной день и завершит работу без отправки. При этом 27-го (в воскресенье) и 28-го (в понедельник) задание просто не запустится, так как расписание жестко ограничивало работу только 26-м числом.
При тестировании регламентных заданий часто допускают ошибку, пытаясь использовать метод Сообщить() для вывода отладочной информации. Помните: фоновые задания выполняются на сервере вне связи с клиентским приложением, поэтому все вызовы Сообщить() будут проигнорированы платформой и уйдут «в никуда».
Для надежного контроля работы регламентных заданий используйте два инструмента:
Используйте метод ЗаписьЖурналаРегистрации() для фиксации каждого шага. Это позволит администраторам базы данных оперативно диагностировать любые сбои в процессе обмена без привлечения разработчика и запуска конфигуратора.
Для того чтобы зайти отладчиком в процедуру регламентного задания и пройтись по коду по шагам:
-debug).