В процессе автоматизации бизнес-процессов на платформе «1С:Предприятие 8» часто возникает задача оперативного информирования сотрудников о ключевых событиях в базе данных. Например, при проведении приходной накладной менеджером склада необходимо мгновенно отправить уведомление или интерактивный вопрос сотруднику отдела закупок: «Поступила новая продукция! Желаете ознакомиться с документом?».
Главная сложность этой задачи в том, что в системе 1С процессы проведения документов выполняются транзакционно и синхронно. Мы не можем напрямую из транзакции проведения вызвать диалоговое окно или предупреждение на экране другого пользователя. Любые модальные вызовы заблокируют работу текущего сеанса, а до сеанса получателя сигнал просто не дойдет. В этой статье мы подробно разберем, как решить эту задачу в конфигурации «Управление производственным предприятием 1.3» (обычные формы) с помощью нескольких надежных архитектурных подходов.
Давайте проанализируем ситуацию. Когда пользователь «А» проводит документ (например, ПоступлениеТоваровУслуг), код выполняется на сервере (или в серверной части файловой базы) в контексте его сеанса. Пользователь «Б» в этот момент работает в своем изолированном сеансе. Прямого канала передачи данных между активными сеансами клиентов (особенно в обычных формах без использования современных веб-сокетов системы взаимодействия) не существует.
Для решения этой проблемы нам необходимо использовать асинхронную модель доставки сообщений. Схема работы выглядит следующим образом:
Выясним, какие инструменты предоставляет нам платформа 1С для реализации этой схемы в рамках конфигурации УПП 1.3.
---
Это самый гибкий и легко контролируемый способ. Мы создадим простой регистр сведений для хранения очереди сообщений и настроим периодический опрос этого регистра на клиенте у каждого пользователя.
В конфигураторе создадим новый непериодический независимый регистр сведений. Назовем его, например, _ОповещенияПользователей.
Настроим структуру регистра:
Получатель (тип СправочникСсылка.Пользователи) — указывает, кому адресовано сообщение.ДокументСсылка (тип ДокументСсылка или составной тип из нужных документов) — ссылка на объект-источник уведомления.Прочитано (тип Булево) — признак того, что пользователь уже отреагировал на сообщение.ТекстСообщения (тип Строка, длина неограниченна) — описание сути вопроса.ДатаСоздания (тип Дата) — для сортировки и контроля актуальности.Рассмотрим код, который необходимо разместить в модуле объекта документа (например, в процедуре ОбработкаПроведения) или в подписке на событие проведения. Выясним, как программно определить получателя и записать данные в регистр:
Процедура ПриПроведенииЗаписатьОповещение(Отказ, РежимПроведения)
// Определим получателя. Для примера возьмем ответственного за закупки
// или ответственное лицо, указанное в настройках.
ПолучательОповещения = НайтиОтветственногоМенеджера(ЭтотОбъект.Контрагент);
Если Не ЗначениеЗаполнено(ПолучательОповещения) Тогда
Возврат; // Если получатель не определен, ничего не делаем
КонецЕсли;
// Записываем данные в наш регистр сведений
НаборЗаписей = РегистрыСведений._ОповещенияПользователей.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Получатель.Установить(ПолучательОповещения);
НаборЗаписей.Отбор.ДокументСсылка.Установить(ЭтотОбъект.Ссылка);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Получатель = ПолучательОповещения;
НоваяЗапись.ДокументСсылка = ЭтотОбъект.Ссылка;
НоваяЗапись.Прочитано = Ложь;
НоваяЗапись.ТекстСообщения = "Поступила новая продукция от контрагента " + ЭтотОбъект.Контрагент + "! Ознакомиться с документом?";
НоваяЗапись.ДатаСоздания = ТекущаяДата();
Попытка
НаборЗаписей.Записать(Истина);
Исключение
ЗаписьЖурналаРегистрации("Оповещения", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Теперь нам нужно заставить клиентское приложение каждого пользователя периодически заглядывать в наш регистр. Для этого используется метод ПодключитьОбработчикОжидания().
Так как мы работаем в обычном приложении, инициализацию обработчика нужно производить в Модуле обычного приложения в процедуре ПриНачалеРаботыСистемы. Проанализируем пример кода:
Процедура ПриНачалеРаботыСистемы()
// Выполняем стандартные процедуры запуска УПП...
// Подключаем наш обработчик проверки оповещений
// Имя процедуры: "ПроверитьНовыеОповещения"
// Интервал: 60 секунд (1 минута)
// Параметр "Однократно": Ложь (выполнять циклически)
ПодключитьОбработчикОжидания("ПроверитьНовыеОповещения", 60, Ложь);
КонецПроцедуры
Процедура, вызываемая обработчиком ожидания, должна находиться в Модуле обычного приложения или в любом глобальном общем модуле с флагом "Клиент". Она должна быть объявлена с ключевым словом Экспорт и не должна содержать параметров.
Разберем детально логику этой процедуры:
Процедура ПроверитьНовыеОповещения() Экспорт
#Если Клиент Тогда
// Получаем текущего авторизованного пользователя системы
ТекущийПользователь = ПараметрыСеанса.ТекущийПользователь;
// Формируем запрос к регистру сведений для поиска непрочитанных записей
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Оповещения.ДокументСсылка КАК ДокументСсылка,
| Оповещения.ТекстСообщения КАК ТекстСообщения
|ИЗ
| РегистрСведений._ОповещенияПользователей КАК Оповещения
|ГДЕ
| Оповещения.Получатель = &ТекущийПользователь
| И НЕ Оповещения.Прочитано";
Запрос.УстановитьПараметр("ТекущийПользователь", ТекущийПользователь);
ТаблицаОповещений = Запрос.Выполнить().Выгрузить();
Для Каждого СтрокаОповещения Из ТаблицаОповещений Цикл
// Задаем интерактивный вопрос пользователю
Ответ = Вопрос(СтрокаОповещения.ТекстСообщения, РежимДиалогаВопрос.ДаНет, 0,, "Новое уведомление!");
Если Ответ = КодВозвратаДиалога.Да Тогда
// Открываем форму проведенного документа
ОткрытьЗначение(СтрокаОповещения.ДокументСсылка);
КонецЕсли;
// Фиксируем факт прочтения в базе данных
ЗафиксироватьПрочтениеОповещения(ТекущийПользователь, СтрокаОповещения.ДокументСсылка);
КонецЦикла;
#КонецЕсли;
КонецПроцедуры
Процедура ЗафиксироватьПрочтениеОповещения(Пользователь, Документ)
НаборЗаписей = РегистрыСведений._ОповещенияПользователей.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Получатель.Установить(Пользователь);
НаборЗаписей.Отбор.ДокументСсылка.Установить(Документ);
НаборЗаписей.Прочитать();
Если НаборЗаписей.Количество() > 0 Тогда
НаборЗаписей[0].Прочитано = Истина;
НаборЗаписей.Записать(Истина);
КонецЕсли;
КонецПроцедуры
---
Зачем изобретать велосипед с созданием регистров, если в конфигурации УПП 1.3 уже заложен мощный механизм бизнес-процессов и задач? Использование штатных задач имеет весомое преимущество: платформа и конфигурация уже содержат встроенные средства адресации, списки задач для каждого пользователя и механизмы контроля их выполнения.
Посмотрим на алгоритм реализации через механизм задач:
В конфигурации УПП 1.3 есть стандартный объект метаданных — задача ЗадачиИсполнителя. При проведении документа мы можем программно породить новую задачу для определенного адресата.
Процедура СоздатьЗадачуДляМенеджера(ДокументСсылка, Исполнитель)
НоваяЗадача = Задачи.ЗадачиИсполнителя.СоздатьЗадачу();
НоваяЗадача.Дата = ТекущаяДата();
НоваяЗадача.Наименование = "Ознакомиться с поступлением продукции № " + ДокументСсылка.Номер;
НоваяЗадача.Исполнитель = Исполнитель;
НоваяЗадача.Предмет = ДокументСсылка; // В УПП реквизит "Предмет" имеет составной тип документов
НоваяЗадача.Важность = Перечисления.ВажностьЗадач.Обычная;
НоваяЗадача.Описание = "Просим вас проверить документ поступления и согласовать спецификацию.";
Попытка
НоваяЗадача.Записать();
Исключение
ЗаписьЖурналаРегистрации("СозданиеЗадачи", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
В УПП 1.3 существует штатный список задач текущего пользователя. Чтобы пользователь мгновенно узнавал о появлении новой задачи, мы можем пойти двумя путями:
Для реализации второго варианта используем метод ПоказатьОповещениеПользователя(), доступный в современных версиях платформы 8.3:
Процедура ПроверитьНовыеЗадачи() Экспорт
#Если Клиент Тогда
ТекущийПользователь = ПараметрыСеанса.ТекущийПользователь;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Задачи.Ссылка КАК ЗадачаСсылка,
| Задачи.Наименование КАК Наименование,
| Задачи.Предмет КАК Предмет
|ИЗ
| Задача.ЗадачиИсполнителя КАК Задачи
|ГДЕ
| Задачи.Исполнитель = &ТекущийПользователь
| И НЕ Задачи.Выполнена";
Запрос.УстановитьПараметр("ТекущийПользователь", ТекущийПользователь);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Формируем красивое ненавязчивое облачко-оповещение в углу экрана
ТекстОповещения = "Нажмите для открытия задачи";
НавигационнаяСсылка = ПолучитьНавигационнуюСсылку(Выборка.Предмет);
ПоказатьОповещениеПользователя(
"Вам назначена новая задача!",
НавигационнаяСсылка,
Выборка.Наименование,
БиблиотекаКартинок.Информация
);
КонецЦикла;
#КонецЕсли;
КонецПроцедуры
---
В УПП 1.3 встроена развитая CRM-подсистема. Оповещения сотрудников можно элегантно настроить с помощью встроенного механизма напоминаний по документу Событие.
Разберем, как активировать и использовать этот механизм:
Процедура СоздатьСобытиеНапоминание(ДокументСсылка, ПользовательПолучатель)
СобытиеОбъект = Документы.Событие.СоздатьДокумент();
СобытиеОбъект.Дата = ТекущаяДата();
СобытиеОбъект.ВидСобытия = Перечисления.ВидыСобытий.Напоминание;
СобытиеОбъект.Ответственный = ПользовательПолучатель;
СобытиеОбъект.Содержание = "Поступили товары по накладной " + ДокументСсылка + ". Проверьте цены.";
СобытиеОбъект.Предмет = ДокументСсылка;
СобытиеОбъект.НачалоСобытия = ТекущаяДата();
СобытиеОбъект.ОкончаниеСобытия = ТекущаяДата() + 300; // Напоминание актуально в течение 5 минут
// Включаем признак напоминания
СобытиеОбъект.Напомнить = Истина;
СобытиеОбъект.ВремяНапоминания = ТекущаяДата();
Попытка
СобытиеОбъект.Записать(РежимЗаписиДокумента.Запись);
Исключение
ЗаписьЖурналаРегистрации("CRM.Напоминание", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Результат: Стандартный внутренний механизм УПП сам обнаружит эту запись и выведет пользователю-получателю встроенную форму «Напоминания о событиях». Пользователю будет доступна кнопка перехода непосредственно к самой накладной.
---
Проанализируем плюсы и минусы каждого подхода, чтобы сделать осознанный выбор в зависимости от специфики бизнес-процессов на вашем предприятии:
| Критерий сравнения | Собственный регистр + Обработчик ожидания | Штатные Задачи (Бизнес-процессы) | CRM-механизм (Документ "Событие") |
|---|---|---|---|
| Сложность разработки | Средняя (требуется создавать новые метаданные и писать логику опроса) | Низкая (используются готовые объекты конфигурации УПП) | Минимальная (готовые объекты и штатный встроенный опросник) |
| Нагрузка на СУБД | Может возрастать при некорректно настроенных индексах и малом интервале опроса | Умеренная (индексы по задачам оптимизированы платформой) | Минимальная (механизм глубоко протестирован разработчиками 1С) |
| Интерактивность | Максимальная (можно задать любой сложный вопрос с выбором действий "Да/Нет/Отмена") | Высокая (задача имеет ссылки на документы, исполнителей и историю) | Средняя (выводится классическое окно напоминания 1С) |
| Возможность потери сообщения | Минимальная (пока флаг Прочитано не установлен, система продолжит напоминать) |
Исключена (задача висит в списке до тех пор, пока ее не пометят выполненной) | Средняя (пользователь может случайно «отложить все» или закрыть окно напоминания) |
---
Независимо от выбранного способа, всегда придерживайтесь следующих лучших практик разработки:
Получатель и Прочитано. Это позволит СУБД мгновенно выдавать нужные строки без полного сканирования таблиц.Мы подробно разобрали, как реализовать интерактивное общение между пользователями в 1С:УПП 1.3. Применение стандартных задач или создание собственного точечного регистра уведомлений с обработчиком ожидания позволит вам сделать интерфейс программы живым, интерактивным и ориентированным на командную работу сотрудников.