Как обработать отказ записи документа на сервере и показать диалог на клиенте в 1С?

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

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

Но что делать, если мы хотим не просто выдать ошибку, а интерактивно помочь пользователю? Например, показать окно: "Товара не хватает на этом складе, но он есть на других. Хотите создать перемещение?". Разберем подробно, как можно решить эту задачу, рассмотрим правильные архитектурные подходы и нестандартные технические приемы.

Архитектурно правильный подход: Проверки до начала транзакции

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

Проанализируем правильный паттерн взаимодействия, при котором логика проверок переносится на уровень формы документа:

  1. В клиентском модуле формы переопределяем событие ПередЗаписью(Отказ, ПараметрыЗаписи).
  2. Из этого события делаем предварительный вызов сервера для проверки возможных проблем (например, проверяем остатки и резервы до начала фактической записи).
  3. Если серверный метод возвращает информацию о нехватке, на клиенте устанавливаем Отказ = Истина, тем самым прерывая стандартный процесс записи.
  4. Выводим пользователю интерактивный диалог (используя методы показа вопросов или открытие вспомогательной формы).
  5. После того как пользователь принял решение (например, нажал "Снять с резерва"), программно вызываем метод формы Записать(ПараметрыЗаписи).
  6. В структуру ПараметрыЗаписи передаем специальный флаг (например, ПараметрыЗаписи.Вставить("АвтоСнятиеРезерва", Истина);).
  7. Серверные обработчики ПередЗаписьюНаСервере или ПриЗаписиНаСервере перехватывают этот параметр и корректно модифицируют данные записываемого объекта перед помещением в базу данных.

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

Нестандартный подход: Передача сигнала через исключение

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

Разберем реализацию этого алгоритма по шагам.

Шаг 1. Передача идентификатора формы в объект.

В модуле формы в событии ПередЗаписьюНаСервере нам нужно сохранить уникальный идентификатор текущей формы в дополнительные свойства объекта. Это позволит нам потом понять, из какой именно формы была вызвана запись.


Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    ТекущийОбъект.ДополнительныеСвойства.Вставить("ИдентификаторФормы", ЭтаФорма.УникальныйИдентификатор);
КонецПроцедуры

Шаг 2. Генерация исключения в модуле объекта.

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


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Если НехваткаТовара Тогда
        Отказ = Истина;
        // Здесь можно также поместить данные о нехватке во временное хранилище
        ВызватьИсключение "#Суперисключение|НехваткаТовара|АдресХранилища";
    КонецЕсли;
КонецПроцедуры

Шаг 3. Перехват исключения на клиенте.

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


Функция ПолучитьСтэкОшибок(ТекстОшибки, Информация) Экспорт
    ТекстВозврата = ТекстОшибки;
    ТекстВозвратаРасширенный = "";
    ЕстьТекстВозвратаРасширенный = Ложь;
    ТекущаяИнформация = Информация;
    Префикс = "";
    
    Пока Истина Цикл
        ТекстВозвратаРасширенный = ТекстВозвратаРасширенный + Префикс + ТекущаяИнформация.Описание 
            + ?(ЗначениеЗаполнено(ТекущаяИнформация.ИмяМодуля), ". Модуль:" + ТекущаяИнформация.ИмяМодуля, "") 
            + ?(ЗначениеЗаполнено(ТекущаяИнформация.НомерСтроки), ". Строка:" + ТекущаяИнформация.НомерСтроки, "");
            
        ЕстьТекстВозвратаРасширенный = Истина;
        
        Если ТекущаяИнформация.Причина = Неопределено Тогда
            Прервать;
        КонецЕсли;
        
        ТекущаяИнформация = ТекущаяИнформация.Причина;
        Префикс = Символы.ПС;
    КонецЦикла;
    
    Возврат ?(ЕстьТекстВозвратаРасширенный, ТекстВозвратаРасширенный, ТекстВозврата);
КонецФункции

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

В этом коде мы проверяем текст ошибки на наличие нашей метки #Суперисключение. Установив СтандартнаяОбработка = Ложь, мы подавляем системное окно. Далее мы можем вызвать клиентский метод нужной формы, чтобы открыть меню с вариантами действий.

Ограничения подхода с исключениями

Важно помнить, что этот подход имеет серьезные ограничения:

  1. При вызове исключения происходит жесткий откат транзакции СУБД. При частом возникновении это может негативно сказаться на производительности.
  2. Если документ перепроводится программно (например, при групповом перепроведении за месяц) или загружается через обмен (РИБ), вызов интерактивных диалогов недопустим. Он приведет к краху фонового задания.

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

Альтернатива: Объект "СообщениеПользователю"

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

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

← На главную