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