Как правильно использовать «ПодключитьОбработчикОжидания» в 1С для периодической проверки документов и вывода предупреждений?

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

Многие задачи в 1С требуют периодического выполнения определенных действий, например, проверки состояния документов, вывода напоминаний (поможет настройка оповещений по расписанию и произвольным условиям) или обновления информации. Для реализации таких сценариев в управляемом приложении мы часто используем функцию ПодключитьОбработчикОжидания(). Однако, ее применение имеет свои особенности, особенно в контексте клиент-серверного взаимодействия. В этой статье мы подробно разберем, как правильно подключить и использовать обработчик ожидания, а также как выполнять серверные операции (например, запросы к базе данных) из клиентского кода, чтобы ваша система работала эффективно и без ошибок.

Что такое ПодключитьОбработчикОжидания() и для чего он нужен?

Функция ПодключитьОбработчикОжидания() позволяет зарегистрировать процедуру, которая будет периодически выполняться на клиенте через заданный интервал времени — для этого подойдёт система автоматического отслеживания ошибок и уведомления ответственных. Вызов этой процедуры происходит только в моменты "простоя" системы, то есть когда пользователь неактивен, и никакие другие операции не выполняются. Это идеальный инструмент для реализации фоновых задач, не требующих немедленного выполнения и не мешающих работе пользователя.

Рассмотрим синтаксис функции:


ПодключитьОбработчикОжидания(<ИмяПроцедуры>, <Интервал>, [<Однократно>]);

Где:

  1. <ИмяПроцедуры> (обязательный параметр, тип: Строка): Это строковое имя экспортируемой процедуры, которая будет вызываться. Важно, чтобы это имя точно совпадало с именем вашей процедуры.
  2. <Интервал> (обязательный параметр, тип: Число): Интервал в секундах, через который система будет пытаться вызвать процедуру. Например, значение 60 будет означать вызов каждые 60 секунд.
  3. <Однократно> (необязательный параметр, тип: Булево): Если мы установим этот параметр в Истина, обработчик будет вызван только один раз, а затем автоматически отключится. По умолчанию этот параметр равен Ложь, что обеспечивает многократный (повторяющийся) вызов обработчика.

Мы должны учитывать, что время вызова обработчика может немного варьироваться, так как он срабатывает только в период бездействия пользователя — для этих целей есть автоматизация согласования и утверждения документов с контролем статусов. Если пользователь активно работает с программой, обработчик будет отложен до момента "простоя".

Требования к процедуре-обработчику

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

  1. Процедура должна быть экспортируемой: Мы объявляем процедуру с ключевым словом Экспорт. Это делает ее видимой и доступной для вызова извне модуля, в данном случае – для функции ПодключитьОбработчикОжидания().
  2. Расположение на клиенте: Процедура должна выполняться на клиентской стороне. Для общих модулей это означает, что у модуля должны быть установлены флаги "Клиент (управляемое приложение)" (для управляемых форм) или "Клиент" (для обычных форм). Сама процедура при этом должна быть помечена директивой &НаКлиенте. Также процедура может находиться непосредственно в модуле управляемого приложения или в модуле формы. Это гарантирует, что код будет выполняться на компьютере пользователя, а не на сервере.
  3. Без параметров: Это критически важное условие! Процедура, передаваемая в ПодключитьОбработчикОжидания(), не должна иметь никаких формальных параметров. Нарушение этого правила является одной из самых частых причин ошибки "Недопустимое значение параметра (параметр номер '1')". Мы не можем передавать в нее никакие значения напрямую через вызов ПодключитьОбработчикОжидания(). Если вам нужны какие-либо данные для работы процедуры, их придется получать внутри самой процедуры (например, через серверные вызовы, глобальные переменные или параметры сеанса).

Где разместить вызов ПодключитьОбработчикОжидания()?

Выбор места для подключения обработчика ожидания зависит от сценария его использования. Если задача должна активироваться сразу при запуске программы для пользователя и работать в фоновом режиме на протяжении всего сеанса, наиболее подходящим местом является процедура ПриНачалеРаботыСистемы() — для этого подойдёт инструмент для автоматического поиска ошибок учета. Эта процедура находится в модуле управляемого приложения и выполняется один раз при старте клиентского приложения.

Рассмотрим пример размещения вызова в модуле управляемого приложения:


// Модуль управляемого приложения

Процедура ПриНачалеРаботыСистемы()

    // Здесь мы можем проверить условия для подключения обработчика
    // Например, только для определенных пользователей или ролей.
    // Об этом мы поговорим подробнее далее.

    // Пример: подключение обработчика ожидания для проверки документов
    ПодключитьОбработчикОжидания("ПроверитьДокументыЖдущиеПодтверждения", 60); // Вызов каждые 60 секунд

КонецПроцедуры

Обратите внимание, что мы передаем имя процедуры как строку — для этого подойдёт гибкое согласование документов с уведомлением ответственных. Система будет искать эту процедуру в глобальной области видимости, то есть либо в самом модуле приложения, либо в глобальных общих модулях.

Где разместить процедуру-обработчик ПроверитьДокументыЖдущиеПодтверждения()?

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

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

Если мы выбираем общий модуль, необходимо установить для него следующие флаги в свойствах конфигуратора:

Пример процедуры в глобальном общем модуле:


// Общий модуль (например, "КлиентскиеФоновыеПроцедуры")
// Флаги в свойствах модуля: Клиент (управляемое приложение) - ✅, Глобальный - ✅

&НаКлиенте
Процедура ПроверитьДокументыЖдущиеПодтверждения() Экспорт
    // Здесь будет основной клиентский код обработчика ожидания.
    // Но что делать, если для проверки нам нужно получить данные из базы данных?
    // Об этом мы поговорим в следующем разделе.
КонецПроцедуры

Клиент-серверное взаимодействие: как выполнить запрос из обработчика ожидания?

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

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

Типовой подход для организации такого взаимодействия выглядит следующим образом:

  1. Клиентская процедура-обработчик (помеченная директивой &НаКлиенте) вызывает серверную экспортную функцию или процедуру (помеченную директивой &НаСервере или &НаСервереБезКонтекста).
  2. Серверная функция/процедура выполняет все необходимые действия с данными: создает запросы, считывает объекты, записывает информацию в базу и т.д.
  3. Серверная функция (если это функция) возвращает результат на клиент, который затем обрабатывается клиентской процедурой (например, выводит предупреждение пользователю).

Рассмотрим пример, когда нам нужно проверить регистр сведений на сервере и получить результат на клиенте.

Получение данных текущего пользователя и условий

Изначально автор вопроса хотел использовать условие для подключения обработчика:


Если ПараметрыСеанса.ТекущийПользователь.ОтветственныйРуководительДляРасхКассовыхДокументов Тогда
    ПодключитьОбработчикОжидания("ПроверитьДокументовЖдушихПотверждение", 60);
КонецЕсли;

Здесь есть несколько важных нюансов, которые мы должны учесть:

  1. ПараметрыСеанса: Эти параметры хранятся на сервере. Непосредственное обращение к ним с клиента (например, из модуля формы или общего модуля с флагом "Клиент") в управляемом приложении вызовет неявный серверный вызов. Это не всегда оптимально с точки зрения производительности, особенно если такой вызов происходит часто. Кроме того, параметр сеанса должен быть корректно инициализирован.
  2. Инициализация параметра сеанса: Если параметр сеанса ТекущийПользователь (или его реквизит ОтветственныйРуководительДляРасхКассовыхДокументов) не был инициализирован в процедуре УстановкаПараметровСеанса() модуля сеанса, то при первом обращении к ним они могут оказаться пустыми, неопределенными или вызвать ошибку. Всегда убедитесь, что ваши параметры сеанса инициализируются корректно при старте сеанса.
  3. Получение реквизита пользователя: Чтобы получить значение реквизита справочника Пользователи для текущего пользователя (например, значение булевой галки "ОтветственныйРуководительДляРасхКассовыхДокументов"), нам потребуется серверный вызов. Даже если параметр сеанса ТекущийПользователь уже является ссылкой на элемент справочника, для чтения его реквизитов (которые физически хранятся в базе данных) с клиента потребуется дополнительный серверный вызов.

Какой подход будет лучшим? Мы рекомендуем создать отдельную серверную экспортную функцию в каком-либо общем модуле (например, ПроверкаДокументовНаСервере или УправлениеПользователямиСервер). Эта функция будет выполнять всю логику проверки условий на сервере и возвращать на клиент только булево значение (Истина/Ложь) или другие необходимые данные.

Пример серверной функции для проверки условий:


// Общий модуль (например, "ПроверкаДокументовНаСервере")
// Флаги в свойствах модуля: Сервер - ✅, ВызовСервера - ✅

&НаСервере
Функция ЯвляетсяЛиТекущийПользовательОтветственнымРуководителем() Экспорт

    // Проверяем, инициализирован ли параметр сеанса ТекущийПользователь
    Если ПараметрыСеанса.ТекущийПользователь <> Неопределено
     И ТипЗнч(ПараметрыСеанса.ТекущийПользователь) = Тип("СправочникСсылка.Пользователи") Тогда
        Попытка
            // Получаем объект пользователя для доступа к его реквизитам
            // Если реквизит ОтвественныйРуководительДляРасхКассовыхДокументов существует
            // и имеет тип Булево, мы можем получить его значение.
            Возврат ПараметрыСеанса.ТекущийПользователь.ОтветственныйРуководительДляРасхКассовыхДокументов;
        Исключение
            // Обрабатываем возможные ошибки, если реквизит не найден
            // или у пользователя нет прав для его чтения
            Возврат Ложь;
        КонецПопытки;
    КонецЕсли;
    // Если параметр сеанса не инициализирован или не является ссылкой на пользователя
    Возврат Ложь;

КонецФункции

Теперь, в процедуре ПриНачалеРаботыСистемы(), вызов для проверки условия будет выглядеть значительно чище и корректнее:


// Модуль управляемого приложения

Процедура ПриНачалеРаботыСистемы()

    // Вызываем серверную функцию для проверки условия
    Если ПроверкаДокументовНаСервере.ЯвляетсяЛиТекущийПользовательОтветственнымРуководителем() Тогда
        ПодключитьОбработчикОжидания("ПроверитьДокументыЖдущиеПодтверждения", 60);
    КонецЕсли;

КонецПроцедуры

Дополнительный вариант (если используется БСП или типовые подсистемы):

В типовых конфигурациях, основанных на Библиотеке стандартных подсистем (БСП), часто используются служебные функции для получения настроек пользователя. Например, для получения значения настройки пользователя, которое хранится как реквизит справочника "Пользователи" или в планах видов характеристик "НастройкиПользователей", можно использовать функции общего модуля УправлениеПользователями:


// Пример использования функций БСП в модуле управляемого приложения
// Предполагается, что модуль УправлениеПользователямиКлиентСервер есть и доступен.

Процедура ПриНачалеРаботыСистемы()

    // Проверим, инициализирован ли ТекущийПользователь в ПараметрахСеанса
    // и является ли он ОтветственнымРуководителем, используя служебную функцию
    Если ОбщегоНазначенияКлиентСервер.ЗначениеЗаполнено(ПараметрыСеанса.ТекущийПользователь) Тогда
        Если УправлениеПользователями.ПолучитьЗначениеПоУмолчанию(ПараметрыСеанса.ТекущийПользователь,
                                                            "ОтветственныйРуководительДляРасхКассовыхДокументов") Тогда
            ПодключитьОбработчикОжидания("ПроверитьДокументыЖдущиеПодтверждения", 60);
        КонецЕсли;
    КонецЕсли;

КонецПроцедуры

Этот подход предпочтительнее, если вы работаете в конфигурации, использующей БСП, так как он обеспечивает согласованность с существующей архитектурой.

Реализация основной логики проверки документов (пошагово)

Теперь, когда мы понимаем все нюансы, давайте соберем весь пазл и пошагово реализуем полную схему для решения задачи: "при каждых 60 секундах программа проверяла, если в регистре сведений есть запись где Ответственный = ТекущийПользователь И Подтверждено = Ложь, тогда выводить предупреждение что нужно подтвердить этих документов".

  1. Подключение обработчика ожидания в модуле управляемого приложения:

    Мы размещаем вызов ПодключитьОбработчикОжидания() в процедуре ПриНачалеРаботыСистемы(). Здесь мы также добавим проверку условия (является ли текущий пользователь ответственным руководителем) с помощью серверной функции, чтобы обработчик запускался только для нужных пользователей.

    
    // Модуль управляемого приложения
    
    Процедура ПриНачалеРаботыСистемы()
    
        // Вызываем серверную функцию для проверки, является ли текущий пользователь
        // ответственным руководителем. Эта функция должна находиться
        // в общем модуле с флагами "Сервер", "ВызовСервера".
        Если ПроверкаДокументовНаСервере.ЯвляетсяЛиТекущийПользовательОтветственнымРуководителем() Тогда
            ПодключитьОбработчикОжидания("ПроверитьДокументыЖдущиеПодтверждения", 60); // Запускаем каждые 60 секунд
        КонецЕсли;
    
    КонецПроцедуры
    
  2. Создание клиентской процедуры-обработчика:

    Мы создадим новый общий модуль, например, "ПроверкаДокументовКлиент". Для него необходимо установить флаги "Клиент (управляемое приложение)" и "Глобальный". В этом модуле напишем нашу клиентскую процедуру ПроверитьДокументыЖдущиеПодтверждения().

    
    // Общий модуль: ПроверкаДокументовКлиент
    // Флаги в свойствах модуля: Клиент (управляемое приложение) - ✅, Глобальный - ✅
    
    &НаКлиенте
    Процедура ПроверитьДокументыЖдущиеПодтверждения() Экспорт
    
        // Создаем массив для получения списка документов с сервера
        Перем СписокНеподтвержденныхДокументов;
    
        // Вызываем серверную функцию, которая выполнит запрос к регистру сведений
        // и вернет список документов для подтверждения.
        // Эта функция должна находиться в общем модуле с флагами "Сервер", "ВызовСервера".
        Если ПроверкаДокументовНаСервере.ПолучитьНеподтвержденныеДокументыДляТекущегоПользователя(СписокНеподтвержденныхДокументов) Тогда
            // Если есть неподтвержденные документы, формируем и выводим предупреждение пользователю.
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Уважаемый руководитель! Вам необходимо подтвердить следующие документы:";
            Для Каждого Документ Из СписокНеподтвержденныхДокументов Цикл
                Сообщение.Текст = Сообщение.Текст + Символы.ПС + "  - " + Документ.Представление;
            КонецЦикла;
            Сообщение.ВидСообщения = ВидСообщенияПользователю.Информация;
            Сообщение.Заголовок = "Срочное подтверждение документов!";
            Сообщение.УстановитьИдентификатор("НеподтвержденныеДокументы"); // Позволяет уникально идентифицировать сообщение
            Сообщение.Сообщить();
    
            // Мы также можем предоставить пользователю возможность сразу перейти к списку этих документов.
            // Например, открыть форму списка или отчета.
            // Пример открытия формы списка документов с отбором:
            // Форма = ПолучитьФорму("Документ.РасходныйКассовыйОрдер.ФормаСписка");
            // Отбор = Форма.Элементы.Список.Отбор;
            // Отбор.Элементы.Очистить();
            // ЭлементОтбора = Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
            // ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
            // ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;
            // ЭлементОтбора.ПравоеЗначение = Новый СписокЗначений;
            // Для Каждого ДокСсылка Из СписокНеподтвержденныхДокументов Цикл
            //     ЭлементОтбора.ПравоеЗначение.Добавить(ДокСсылка);
            // КонецЦикла;
            // ЭлементОтбора.Использование = Истина;
            // Форма.Открыть();
    
        КонецЕсли;
    
    КонецПроцедуры
    
  3. Создание серверного общего модуля для проверки условий и получения данных:

    Мы создадим новый общий модуль, назовем его "ПроверкаДокументовНаСервере". Для него необходимо установить флаги "Сервер" и "ВызовСервера". В нем будут находиться функции, которые выполняют работу с базой данных.

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

Таким образом, мы построили полноценную схему: при запуске системы мы проверяем, нужен ли вообще обработчик для этого пользователя, а затем каждые 60 секунд клиентский код вызывает сервер, который выполняет запрос к базе данных и возвращает список документов, если таковые имеются, для отображения предупреждения.

Типичные ошибки и их устранение

В процессе работы с ПодключитьОбработчикОжидания() разработчики часто сталкиваются с рядом стандартных ошибок. Давайте рассмотрим наиболее распространенные из них и способы их устранения.

Ошибка: "Недопустимое значение параметра (параметр номер '1')"

Ошибка: "Переменная не определена" или процедура не найдена

Опечатки в имени процедуры

Отключение обработчика ожидания

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

Синтаксис этой функции прост:


ОтключитьОбработчикОжидания(<ИмяПроцедуры>);

Где <ИмяПроцедуры> – это строковое имя процедуры, которое мы использовали при подключении.

Пример отключения:


// Например, при закрытии формы или при изменении настроек пользователя
// в модуле управляемого приложения в ПриЗавершенииРаботыСистемы()
ОтключитьОбработчикОжидания("ПроверитьДокументыЖдущиеПодтверждения");

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

Заключение

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

← На главную