Как вывести сообщение пользователю из серверной подписки на событие ПередЗаписью

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

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

Ситуация осложняется, если форма документа закрывается сразу после записи, и обычные механизмы вывода сообщений в форму не успевают сработать. Разберем подробно, как можно решить эту задачу, используя архитектурные возможности платформы 1С.

Причина ограничения: почему нельзя просто вызвать окно с сервера?

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

Метод 1: Использование Системы взаимодействия (Collaboration System)

Проанализируем наиболее современный способ — использование Системы взаимодействия. Этот механизм позволяет серверу отправить сообщение конкретному пользователю "вдогонку". Преимущество в том, что уведомление появится в интерфейсе 1С (колокольчик или всплывающее окно) независимо от того, закрыта форма документа или нет.

Для реализации этого метода нам потребуется выполнить следующие шаги в коде подписки:


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

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

Метод 2: Организация очереди сообщений через Регистр Сведений

Рассмотрим классический подход, который часто называют "Очередь оповещений". Если Система взаимодействия не настроена, мы можем создать механизм передачи сигнала с сервера на клиент через базу данных.

Шаг 1: Создание регистра. Создадим непериодический регистр сведений ОчередьСобытийДляПользователей с измерениями Пользователь (Тип: СправочникСсылка.Пользователи) и ИдентификаторСообщения (Тип: УникальныйИдентификатор), и ресурсом ТекстСообщения (Тип: Строка).

Шаг 2: Запись в регистр из подписки. В серверном коде подписки добавим запись:


МенеджерЗаписи = РегистрыСведений.ОчередьСобытийДляПользователей.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Пользователь = Пользователи.ТекущийПользователь();
МенеджерЗаписи.ИдентификаторСообщения = Новый УникальныйИдентификатор();
МенеджерЗаписи.ТекстСообщения = "Документ успешно обработан подпиской.";
МенеджерЗаписи.Записать();

Шаг 3: Чтение на клиенте. В модуле управляемого приложения (или в основной форме рабочего стола) настроим ПодключаемыйОбработчикОжидания, который будет срабатывать, например, раз в 10 секунд.


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

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

Метод 3: Проброс данных через ДополнительныеСвойства и форму

Выясним причину, по которой автор вопроса не может использовать форму. Форма закрывается. Но если запись инициирована интерактивно, мы можем "поймать" момент возврата управления на клиент. Разберем, как это реализовать через ДополнительныеСвойства объекта.

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

Важный нюанс: Чтобы не дублировать этот код в сотни форм, как опасался автор темы, следует использовать Расширения конфигурации. Рассмотрим, как это упрощает задачу.

Метод 4: Использование расширений для глобального перехвата

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

Посмотрим на пример кода в расширении для формы документа:


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

Метод 5: Системные окна через COM-объекты (WScript.Shell)

Если решение требуется "здесь и сейчас" только для Windows-клиентов и допустимо использование "грубых" методов, можно воспользоваться вызовом системного окна Windows. Этот код сработает из серверного контекста только в том случае, если сервер и клиент — это одна машина (например, файловая база), либо если мы вызываем его через УдаленноеВыполнение.

Однако, правильнее использовать это именно при переходе на клиентский контекст, чтобы окно гарантированно было поверх всех окон ОС:


WshShell = Новый COMОбъект("WScript.Shell");
// Параметры: Текст, Таймаут (0 - ждать нажатия), Заголовок, Тип иконки
WshShell.Popup("Операция завершена успешно!", 0, "Уведомление 1С", 64);

Внимание: Данный метод не рекомендуется для кроссплатформенных решений (Linux, Web-клиент).

Метод 6: ВызватьИсключение (прерывание процесса)

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

Резюме и рекомендации

Подведем итоги нашего анализа:

Выбор конкретного метода зависит от технических условий вашей базы (наличие лицензий на Систему взаимодействия, использование веб-клиента) и критичности сообщения для бизнес-процесса.

← На главную