При разработке в 1С часто возникает ситуация, когда при записи или проведении документа в модуле объекта возникает ошибка (например, нехватка товаров на складе). Стандартное поведение платформы в таком случае — установка флага Отказ = Истина в событии ПередЗаписью модуля объекта. После этого транзакция прерывается, и на клиенте не возникает событие ПослеЗаписи. С сервера отправить данные напрямую на клиент невозможно.
Но что делать, если мы хотим не просто выдать ошибку, а интерактивно помочь пользователю? Например, показать окно: "Товара не хватает на этом складе, но он есть на других. Хотите создать перемещение?". Разберем подробно, как можно решить эту задачу, рассмотрим правильные архитектурные подходы и нестандартные технические приемы.
Попытка организовать интерактивный диалог (показ меню действий, вопросов) из модуля объекта при проведении или записи является в 1С антипаттерном. Модуль объекта работает на сервере, часто внутри транзакции, и не должен ничего знать о клиентском интерфейсе (формах). Жесткое прерывание транзакций чревато проблемами с блокировками и нарушением работы механизмов обмена данными.
Проанализируем правильный паттерн взаимодействия, при котором логика проверок переносится на уровень формы документа:
ПередЗаписью(Отказ, ПараметрыЗаписи).Отказ = Истина, тем самым прерывая стандартный процесс записи.Записать(ПараметрыЗаписи).ПараметрыЗаписи передаем специальный флаг (например, ПараметрыЗаписи.Вставить("АвтоСнятиеРезерва", Истина);).ПередЗаписьюНаСервере или ПриЗаписиНаСервере перехватывают этот параметр и корректно модифицируют данные записываемого объекта перед помещением в базу данных.Для сложных случаев можно вообще скрыть стандартную кнопку "Провести" и создать кастомную команду формы, в которой будет происходить вся логика предварительной валидации и опроса пользователя, и лишь в конце вызываться метод Записать().
Если нет возможности переписывать типовую конфигурацию или выносить объемные проверки из общего модуля в форму, рассмотрим нестандартный (костыльный) метод. Суть заключается в генерации специального исключения на сервере и его перехвате на клиенте в модуле приложения.
Разберем реализацию этого алгоритма по шагам.
Шаг 1. Передача идентификатора формы в объект.
В модуле формы в событии ПередЗаписьюНаСервере нам нужно сохранить уникальный идентификатор текущей формы в дополнительные свойства объекта. Это позволит нам потом понять, из какой именно формы была вызвана запись.
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
ТекущийОбъект.ДополнительныеСвойства.Вставить("ИдентификаторФормы", ЭтаФорма.УникальныйИдентификатор);
КонецПроцедуры
Шаг 2. Генерация исключения в модуле объекта.
В модуле объекта (в событии ПередЗаписью или ОбработкаПроведения), когда мы обнаруживаем нехватку, мы генерируем исключение со специальным тегом, чтобы отличить его от обычных системных ошибок. Если нужно передать какие-то сложные данные, их можно поместить во временное хранилище и передать адрес хранилища в тексте ошибки.
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если НехваткаТовара Тогда
Отказ = Истина;
// Здесь можно также поместить данные о нехватке во временное хранилище
ВызватьИсключение "#Суперисключение|НехваткаТовара|АдресХранилища";
КонецЕсли;
КонецПроцедуры
Шаг 3. Перехват исключения на клиенте.
Теперь проанализируем, как перехватить эту ошибку, чтобы она не показала пользователю страшное окно платформы. Для этого в модуле управляемого приложения используем событие ОбработкаОтображенияОшибки. Нам потребуется функция для разбора стека ошибок.
Функция ПолучитьСтэкОшибок(ТекстОшибки, Информация) Экспорт
ТекстВозврата = ТекстОшибки;
ТекстВозвратаРасширенный = "";
ЕстьТекстВозвратаРасширенный = Ложь;
ТекущаяИнформация = Информация;
Префикс = "";
Пока Истина Цикл
ТекстВозвратаРасширенный = ТекстВозвратаРасширенный + Префикс + ТекущаяИнформация.Описание
+ ?(ЗначениеЗаполнено(ТекущаяИнформация.ИмяМодуля), ". Модуль:" + ТекущаяИнформация.ИмяМодуля, "")
+ ?(ЗначениеЗаполнено(ТекущаяИнформация.НомерСтроки), ". Строка:" + ТекущаяИнформация.НомерСтроки, "");
ЕстьТекстВозвратаРасширенный = Истина;
Если ТекущаяИнформация.Причина = Неопределено Тогда
Прервать;
КонецЕсли;
ТекущаяИнформация = ТекущаяИнформация.Причина;
Префикс = Символы.ПС;
КонецЦикла;
Возврат ?(ЕстьТекстВозвратаРасширенный, ТекстВозвратаРасширенный, ТекстВозврата);
КонецФункции
Процедура ОбработкаОтображенияОшибки(ИнформацияОбОшибке, ТребуетсяЗавершениеСеанса, СтандартнаяОбработка)
ОшибкаРазобранная = ПолучитьСтэкОшибок("", ИнформацияОбОшибке);
Если СтрНайти(ОшибкаРазобранная, "#Суперисключение") > 0 Тогда
// Отключаем показ стандартного окна ошибки
СтандартнаяОбработка = Ложь;
// Здесь мы можем распарсить строку, получить адрес из временного хранилища,
// найти нужную форму по идентификатору и запустить в ней интерактивный диалог
КонецЕсли;
КонецПроцедуры
В этом коде мы проверяем текст ошибки на наличие нашей метки #Суперисключение. Установив СтандартнаяОбработка = Ложь, мы подавляем системное окно. Далее мы можем вызвать клиентский метод нужной формы, чтобы открыть меню с вариантами действий.
Важно помнить, что этот подход имеет серьезные ограничения:
Поэтому в модуле объекта обязательно необходимо делать проверку на то, выполняется ли код в интерактивной сессии пользователя, и проверять свойство ОбменДанными.Загрузка перед тем, как генерировать исключение.
Если задача заключается лишь в информировании пользователя о причинах отказа (без создания интерактивных меню), оптимальным решением является использование объекта СообщениеПользователю. Формируя это сообщение на сервере при возникновении отказа, вы можете привязать его к конкретной строке табличной части или реквизиту (используя свойства Поле и ПутьКДанным).
Платформа автоматически передаст эти сообщения на клиент и отобразит их рядом с проблемными полями ввода. Это существенно упрощает пользователю исправление ошибок без необходимости применения сложных архитектурных трюков или костылей с исключениями.