Как передать параметры в обработчик ожидания и организовать отложенную смену статуса документа в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке прикладных решений часто возникает необходимость выполнить определенное действие не сразу, а спустя некоторый промежуток времени. Классический пример — автоматическая смена статуса документа через фиксированный интервал (например, перевод рейса в статус РейсЗавершен ровно через 1 час после статуса СовершилПосадку).

Попытка решить эту задачу «в лоб» через клиентский метод ПодключитьОбработчикОжидания часто приводит разработчиков к двум проблемам: непониманию, как передать контекст (ссылку на документ) в вызываемую процедуру, и архитектурным сбоям, когда сеанс пользователя закрывается раньше времени. Рассмотрим подробно ошибки исходного подхода, правильный способ передачи параметров в обработчик ожидания, а также надежные серверные альтернативы.

Ошибки при попытке передать данные через временное хранилище

Проанализируем исходную попытку передачи ссылки через временное хранилище с фиксированным GUID:


Функция ЗапускОбработчикаОжидания(ДокументСсылка) Экспорт
    СтрокаGUID = "a763cfbb-f94f-4c67-8e13-0e96a3a7f353";
    НовыйGUID = Новый УникальныйИдентификатор(СтрокаGUID);
    Адрес = ПоместитьВоВременноеХранилище("212121115", НовыйGUID);
    ПодключитьОбработчикОжидания("ВыполнениеОбработчикаОжидания", 5, Истина);
КонецФункции

&НаКлиенте
Процедура ВыполнениеОбработчикаОжидания() Экспорт
    СтрокаGUID = "e1cib/tempstorage/fe5a376e-4e52-41b0-a3d5-5637380d3f5a";
    Ссылка = ПолучитьИзВременногоХранилища(СтрокаGUID);
КонецПроцедуры

В этом коде допущено сразу несколько критических ошибок:

  1. Метод ПоместитьВоВременноеХранилище возвращает сформированный системой внутренний URL (например, e1cib/tempstorage/...). Передача второго параметра НовыйGUID не делает адрес хранилища равным этому GUID, а лишь определяет область жизни данных.
  2. В обработчике происходит попытка прочитать данные по жестко зашитой строке, которая никак не связана с созданной ранее областью памяти. В результате метод ПолучитьИзВременногоХранилища возвращает Неопределено.
  3. Данные во временном хранилище, не привязанные к уникальному идентификатору формы, уничтожаются при завершении контекста серверного вызова.

Способ 1. Передача параметров в обработчик ожидания через ОписаниеОповещения

Если задача действительно требует выполнения логики на клиенте (например, визуальное оповещение пользователя), передавать данные через временное хранилище или глобальные переменные не требуется. В платформе 1С:Предприятие 8.3 метод ПодключитьОбработчикОжидания поддерживает работу с объектом ОписаниеОповещения.

Разберем, как передать структуру параметров прямо в процедуру-обработчик:


&НаКлиенте
Процедура ЗапуститьТаймер(ДокументСсылка) Экспорт
    ПараметрыВызова = Новый Структура;
    ПараметрыВызова.Вставить("Документ", ДокументСсылка);
    ПараметрыВызова.Вставить("ВремяСтарта", ТекущаяДата());
    
    Оповещение = Новый ОписаниеОповещения("ОбработатьТаймер", ЭтотОбъект, ПараметрыВызова);
    
    // Подключаем однократный обработчик на 5 секунд
    ПодключитьОбработчикОжидания(Оповещение, 5, Истина);
КонецПроцедуры

&НаКлиенте
Процедура ОбработатьТаймер(ДополнительныеПараметры) Экспорт
    Если ДополнительныеПараметры = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    ДокументСсылка = ДополнительныеПараметры.Документ;
    // Выполняем требуемые действия на сервере
    ВыполнитьСменуСтатусаНаСервере(ДокументСсылка);
КонецПроцедуры

Ограничение подхода: клиентский обработчик ожидания подходит только для коротких интервалов (секунды, несколько минут). Если интервал составляет 1 час, этот подход категорически не подходит. Если пользователь закроет приложение, заблокирует компьютер или произойдет обрыв сети, клиентский таймер сбросится, и статус документа никогда не изменится.

Способ 2. Архитектурно верное решение: Серверная очередь задач и Регламентное задание

Когда действие должно гарантированно произойти на сервере через длительное время (30 минут, 1 час, сутки), применяют механизм очереди задач на базе непериодического регистра сведений и регламентного задания.

Разберем реализацию по шагам:

  1. Создаем регистр сведений ОчередьСменыСтатусов:

    Измерения: Документ (Тип: ДокументСсылка.Рейс).
    Реквизиты: ПлановоеВремя (Тип: Дата), ЦелевойСтатус (Тип: ПеречислениеСсылка.СтатусыРейсов).

  2. Фиксация события (постановка в очередь):

    В момент перевода документа в промежуточный статус записываем в регистр задание на выполнение через 3600 секунд:

    
    Процедура ЗапланироватьСменуСтатуса(ДокументСсылка) Экспорт
        МенеджерЗаписи = РегистрыСведений.ОчередьСменыСтатусов.СоздатьМенеджерЗаписи();
        МенеджерЗаписи.Документ = ДокументСсылка;
        МенеджерЗаписи.ПлановоеВремя = ТекущаяДатаСеанса() + 3600; // Ровно через 1 час
        МенеджерЗаписи.ЦелевойСтатус = Перечисления.СтатусыРейсов.РейсЗавершен;
        МенеджерЗаписи.Записать(Истина);
    КонецПроцедуры
    
  3. Обработка очереди регламентным заданием:

    Создаем регламентное задание с расписанием (выполнять раз в 1 минуту), вызывающее процедуру общего модуля:

    
    Процедура ОбработатьОчередьСтатусов() Экспорт
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ
            |   Очередь.Документ КАК Документ,
            |   Очередь.ЦелевойСтатус КАК ЦелевойСтатус
            |ИЗ
            |   РегистрСведений.ОчередьСменыСтатусов КАК Очередь
            |ГДЕ
            |   Очередь.ПлановоеВремя <= &ТекущееВремя";
        
        Запрос.УстановитьПараметр("ТекущееВремя", ТекущаяДатаСеанса());
        Выборка = Запрос.Выполнить().Выбрать();
        
        Пока Выборка.Следующий() Цикл
            Блокировка = Новый БлокировкаДанных;
            ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ОчередьСменыСтатусов");
            ЭлементБлокировки.УстановитьЗначение("Документ", Выборка.Документ);
            
            НачатьТранзакцию();
            Попытка
                Блокировка.Заблокировать();
                
                // Проверяем актуальность состояния перед изменением
                ДокументОбъект = Выборка.Документ.ПолучитьОбъект();
                Если ДокументОбъект <> Неопределено И ДокументОбъект.Статус = Перечисления.СтатусыРейсов.СовершилПосадку Тогда
                    ДокументОбъект.Статус = Выборка.ЦелевойСтатус;
                    ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
                КонецЕсли;
                
                // Удаляем запись из очереди
                МенеджерЗаписи = РегистрыСведений.ОчередьСменыСтатусов.СоздатьМенеджерЗаписи();
                МенеджерЗаписи.Документ = Выборка.Документ;
                МенеджерЗаписи.Удалить();
                
                ЗафиксироватьТранзакцию();
            Исключение
                ОтменитьТранзакцию();
                ЗаписьЖурналаРегистрации("ФоноваяСменаСтатуса", УровеньЖурналаРегистрации.Ошибка, , Выборка.Документ, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
            КонецПопытки;
        КонецЦикла;
    КонецПроцедуры
    

Способ 3. Запись будущих состояний в периодический регистр сведений

Если бизнес-логика не требует повторного перепроведения документа и генерации проводок через 1 час, а статус используется исключительно для отображения в формах и отчетах, можно обойтись без регламентных заданий.

Для этого используется периодический регистр сведений (периодичность по позиции регистратора или по секундам). В момент совершения посадки в регистр сразу вносятся две записи:


Процедура ЗафиксироватьСтатусыВРегистре(ДокументСсылка) Экспорт
    НаборЗаписей = РегистрыСведений.СтатусыДокументов.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Документ.Установить(ДокументСсылка);
    
    // Запись 1: текущее состояние
    Запись1 = НаборЗаписей.Добавить();
    Запись1.Период = ТекущаяДатаСеанса();
    Запись1.Документ = ДокументСсылка;
    Запись1.Статус = Перечисления.СтатусыРейсов.СовершилПосадку;
    
    // Запись 2: будущее состояние через 1 час
    Запись2 = НаборЗаписей.Добавить();
    Запись2.Период = ТекущаяДатаСеанса() + 3600;
    Запись2.Документ = ДокументСсылка;
    Запись2.Статус = Перечисления.СтатусыРейсов.РейсЗавершен;
    
    НаборЗаписей.Записать(Истина);
КонецПроцедуры

При таком подходе статус документа в любой форме, списке или отчете определяется виртуальной таблицей СрезПоследних(&Период). До наступления назначенного часа срез будет возвращать статус СовершилПосадку, а как только пройдет ровно 60 минут, срез автоматически начнет отдавать РейсЗавершен без участия фоновых заданий или клиентских сеансов.

← На главную