В процессе разработки и сопровождения систем на платформе «1С:Предприятие» часто возникает задача автоматизации рутинных действий пользователей. Например, в конфигурации «Управление торговлей 10.3» (или любых других решениях на обычных и управляемых формах) требуется настроить автоматический запуск рабочих обработок в строго определенные часы: утром в начале рабочего дня (в 08:00) для планирования задач и вечером перед завершением смены (в 17:30) для подготовки отчетности.
Основная сложность реализации заключается в нестабильности сеансов пользователей. В течение дня программа 1С может аварийно завершить работу, компьютер может уйти в спящий режим, либо пользователь сам решит перезапустить систему. Обычный статический таймер ожидания, настроенный при старте системы на фиксированный интервал, в таких условиях неизбежно сбивается. Выясним причину этой нестабильности, разберем по шагам надежные алгоритмы реализации и рассмотрим лучшие практики программирования.
Проанализируем технические ограничения платформы при работе с методом ПодключитьОбработчикОжидания. Разработчики часто совершают ошибку, рассчитывая точный интервал в секундах от текущего момента до целевого времени (например, вычисляют, что до 17:30 осталось 15 400 секунд, и устанавливают этот интервал).
Критическое ограничение платформы: обработчик ожидания срабатывает только тогда, когда система находится в состоянии покоя. Если в расчетную секунду пользователь будет активно проводить тяжелый документ, формировать объемный отчет или на экране будет открыто модальное окно (например, предупреждение системы), точно рассчитанный однократный таймер может не сработать вообще или сработать с огромным опозданием.
Для решения этой проблемы мы применим алгоритм «пульса» — легкий обработчик ожидания, который запускается регулярно (например, каждые 5-10 минут) и проверяет, вошло ли текущее время в целевой интервал запуска.
Разберем пример реализации. В модуле обычного приложения (или управляемого приложения в процедуре ПриНачалеРаботыСистемы) пропишем подключение нашего фонового обработчика:
Процедура ПриНачалеРаботыСистемы()
// Подключаем регулярный "пульс" с интервалом 300 секунд (5 минут)
ПодключитьОбработчикОжидания("ПроверитьНеобходимостьОткрытияФорм", 300);
КонецПроцедуры
Теперь опишем процедуру проверки в глобальном или общем модуле (модуль должен быть с флагом «Клиент»):
Процедура ПроверитьНеобходимостьОткрытияФорм() Экспорт
ТекущееВремя = ТекущаяДата();
ТекущийЧас = Час(ТекущееВремя);
ТекущаяМинута = Минута(ТекущееВремя);
// Переводим время в минуты для удобства сравнения
ВремяВМинутах = ТекущийЧас * 60 + ТекущаяМинута;
// Целевые интервалы запуска (в минутах от начала дня)
// Утреннее открытие: с 08:00 (480 мин.) до 08:15 (495 мин.)
ИнтервалУтро_Начало = 480;
ИнтервалУтро_Конец = 495;
// Вечернее открытие: с 17:30 (1050 мин.) до 17:45 (1065 мин.)
ИнтервалВечер_Начало = 1050;
ИнтервалВечер_Конец = 1065;
// Проверяем вхождение текущего времени в заданные коридоры
Если (ВремяВМинутах >= ИнтервалУтро_Начало И ВремяВМинутах <= ИнтервалУтро_Конец) Тогда
Если Не ФормаУжеОткрываласьСегодня("Утро") Тогда
ОткрытьФормуОбработки("Утро");
КонецЕсли;
ИначеЕсли (ВремяВМинутах >= ИнтервалВечер_Начало И ВремяВМинутах <= ИнтервалВечер_Конец) Тогда
Если Не ФормаУжеОткрываласьСегодня("Вечер") Тогда
ОткрытьФормуОбработки("Вечер");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Если мы хотим минимизировать количество холостых вызовов обработчика ожидания, рассмотрим схему динамического расчета оставшегося времени. При старте сеанса или после каждого срабатывания таймера система будет высчитывать точное количество секунд до ближайшего события (утра или вечера) и переназначать обработчик на этот период.
Посмотрим на пример функции, которая определяет секунды до ближайшей контрольной точки:
Функция ПолучитьСекундыДоБлижайшегоСобытия() Экспорт
ТекущееВремя = ТекущаяДата();
НачалоДня = НачалоДня(ТекущееВремя);
// Задаем контрольные точки времени в секундах от начала дня
ВремяУтро = НачалоДня + 8 * 3600; // 08:00:00
ВремяВечер = НачалоДня + 17.5 * 3600; // 17:30:00
Если ТекущееВремя < ВремяУтро Тогда
// Ближайшее событие — утро текущего дня
Возврат ВремяУтро - ТекущееВремя;
ИначеЕсли ТекущееВремя < ВремяВечер Тогда
// Ближайшее событие — вечер текущего дня
Возврат ВремяВечер - ТекущееВремя;
Иначе
// События на сегодня закончились, планируем на утро следующего дня (+24 часа)
Возврат (ВремяУтро + 86400) - ТекущееВремя;
КонецЕсли;
КонецФункции
Инициализацию и последующий перезапуск обработчика ожидания свяжем в единую цепочку:
Процедура ПриНачалеРаботыСистемы()
ИнтервалОжидания = ПолучитьСекундыДоБлижайшегоСобытия();
// Подключаем однократный обработчик ожидания (параметр Истина)
ПодключитьОбработчикОжидания("ОткрытьФормыПоРасписанию", ИнтервалОжидания, Истина);
КонецПроцедуры
Процедура ОткрытьФормыПоРасписанию() Экспорт
// Выполняем полезное действие — открываем форму
ОткрытьФормуОбработки();
// Сразу планируем следующее событие
ИнтервалОжидания = ПолучитьСекундыДоБлижайшегоСобытия();
ПодключитьОбработчикОжидания("ОткрытьФормыПоРасписанию", ИнтервалОжидания, Истина);
КонецПроцедуры
Если пользователь закроет базу данных в 08:05 и зайдет в нее снова в 08:10, то при использовании любого из вышеописанных методов форма откроется повторно, поскольку текущее время все еще соответствует утреннему интервалу запуска. Чтобы избежать назойливого дублирования окон, необходимо сохранять состояние (факт открытия формы).
Выясним преимущества и недостатки различных механизмов хранения состояния:
ИсторияОткрытияФорм с измерениями Пользователь (СправочникСсылка.Пользователи) и ПериодПроверки (Дата, хранит день без времени), а также ресурсами УтроОткрыто (Булево) и ВечерОткрыто (Булево).Проанализируем пример кода проверки с использованием регистра сведений:
Функция ФормаУжеОткрываласьСегодня(ТипСобытия) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| История.УтроОткрыто КАК УтроОткрыто,
| История.ВечерОткрыто КАК ВечерОткрыто
|ИЗ
| РегистрСведений.ИсторияОткрытияФорм КАК История
|ГДЕ
| История.Пользователь = &ТекущийПользователь
| И История.ПериодПроверки = &НачалоДня";
Запрос.УстановитьПараметр("ТекущийПользователь", ПараметрыСеанса.ТекущийПользователь);
Запрос.УстановитьПараметр("НачалоДня", НачалоДня(ТекущаяДата()));
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Если ТипСобытия = "Утро" Тогда
Возврат Выборка.УтроОткрыто;
ИначеЕсли ТипСобытия = "Вечер" Тогда
Возврат Выборка.ВечерОткрыто;
КонецЕсли;
КонецЕсли;
Возврат Ложь;
КонецФункции
При успешном открытии формы обработки мы должны сделать запись в этот регистр сведений, установив соответствующий флаг в значение Истина.
Внезапное открытие тяжелой формы обработки в 17:30 может вызвать серьезное раздражение у пользователей. Если менеджер в этот момент быстро вводит информацию в документ или общается по телефону с контрагентом, фокус ввода резко переключится на открывшуюся форму, что приведет к потере набранного текста или случайным нажатиям клавиш.
Для повышения удобства работы пользователей заменим прямое открытие формы на создание интерактивного всплывающего оповещения:
// Вместо агрессивного ОткрытьФорму() используем оповещение
ОписаниеОповещения = Новый ОписаниеОповещения("ОбработатьКликНаОповещение", ЭтотОбъект);
ПоказатьОповещениеПользователя(
"Заполнение вечернего отчета",
ОписаниеОповещения,
"Наступило время заполнить отчет за смену. Нажмите сюда для открытия формы.",
БиблиотекаКартинок.Информация
);
В процедуре-обработчике клика мы опишем стандартное открытие нашей формы. Таким образом, у пользователя появится возможность открыть форму в удобный для себя момент времени, не прерывая текущую операцию.
Также, если ваша конфигурация построена на базе Библиотеки стандартных подсистем (БСП), рекомендуется задействовать встроенную подсистему «Напоминания пользователя». Программное добавление записи в регистр напоминаний на 08:00 и 17:30 предоставит пользователю стандартный, гибкий и интуитивно понятный интерфейс управления напоминаниями с возможностью отложить событие на 5, 10 или 15 минут.