Как передать данные в форму 1С при отказе от записи объекта и обойти ограничение события ПослеЗаписиНаСервере?

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

При разработке в среде «1С:Предприятие 8» на управляемых формах разработчики часто сталкиваются с фундаментальным поведением платформы: если в процессе записи объекта (в модуле объекта или в связанных подписках на события) переменная Отказ принимает значение Истина, то транзакция записи откатывается. Как следствие, обработчик формы ПослеЗаписиНаСервере() не выполняется вообще.

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

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

Почему не работает ПослеЗаписиНаСервере() при отказе?

Давайте проанализируем стандартную последовательность вызова серверных обработчиков при интерактивной записи элемента справочника или документа из управляемой формы:

  1. В форме вызывается обработчик ПередЗаписьюНаСервере().
  2. Платформа открывает транзакцию базы данных.
  3. В модуле объекта отрабатывает предопределенное событие ПередЗаписью().
  4. В модуле объекта выполняется событие ПриЗаписи() (и обработка проведения для документов).
  5. В управляемой форме отрабатывает событие ПриЗаписиНаСервере().
  6. Транзакция фиксируется в базе данных.
  7. На сервере вызывается финальное событие формы ПослеЗаписиНаСервере().

Если на шаге 3 или 4 взводится флаг Отказ = Истина, платформа немедленно инициирует откат транзакции СУБД. Все изменения стираются из базы данных, а выполнение серверной цепочки обрывается. Шаги 5, 6 и 7 просто игнорируются платформой, так как сам факт записи признан несостоявшимся.

Рассмотрим подробнее три основных способа обхода этого системного ограничения.

Решение №1: Паттерн отложенного отказа через форму (ПриЗаписиНаСервере)

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

Давайте пошагово реализуем этот алгоритм в коде.

Шаг 1. Модуль управляемой формы документа

В модуле формы мы инициализируем коллекцию дополнительных свойств и перехватываем отказ на этапе записи на сервере:


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

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

Шаг 2. Модуль объекта (документа или справочника)

В модуле объекта мы разделяем логику для программной записи (где формы нет и нужно сразу выдавать жесткий отказ) и интерактивной записи (где мы делегируем отказ форме):


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

Важное примечание для документов: Если ваш объект является документом и записывается в режиме проведения, нам обязательно нужно заблокировать выполнение стандартного обработчика проведения, чтобы система не пыталась формировать движения при наличии отложенного отказа:


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

Решение №2: Программный вызов метода Записать() в Попытке-Исключении на клиенте

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

Давайте разберем, как выглядит этот обходной путь:


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

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

Решение №3: Современный платформенный механизм через исключения и Временное хранилище

Если вы работаете на современных версиях платформы «1С:Предприятие 8.3» (начиная с 8.3.15), вы можете использовать передовой подход, соответствующий стандартам разработки БСП. Этот способ полностью безопасен и не требует искусственного переноса флага отказа.

Алгоритм действий выглядит следующим образом:

  1. В модуле объекта при неуспешной проверке мы сериализуем структуру ошибок и помещаем её во Временное хранилище.
  2. Вызываем исключение специального формата, передавая адрес этого хранилища.
  3. В модуле управляемого приложения в глобальном обработчике ОбработкаОтображенияОшибки() перехватываем эту ошибку, извлекаем данные по адресу хранилища и передаем их в форму для красивой отрисовки.

Давайте посмотрим на программную реализацию этой схемы.

Код в модуле объекта:


Процедура ПриЗаписи(Отказ)
    
    // Проверки провалились
    Если Не ПроверкиПройдены Тогда
        Отказ = Истина;
        
        // Помещаем детальные данные во временное хранилище базы
        АдресХранилища = ПоместитьВоВременноеХранилище(СтруктураДетальныхОшибок, Новый УникальныйИдентификатор);
        
        // Вызываем исключение со специальным префиксом
        ВызватьИсключение "ДЕТАЛЬНЫЙ_ОТКАЗ_ЗАПИСИ:" + АдресХранилища;
    КонецЕсли;
    
КонецПроцедуры

Код в модуле управляемого приложения:


Процедура ОбработкаОтображенияОшибки(ИнформацияОбОшибке, СтандартнаяОбработка, ПереданныеПараметры)
    
    ТекстОшибки = ИнформацияОбОшибке.Описание;
    ПрефиксОшибки = "ДЕТАЛЬНЫЙ_ОТКАЗ_ЗАПИСИ:";
    
    Если Найти(ТекстОшибки, ПрефиксОшибки) > 0 Тогда
        // Отключаем стандартный вывод системного сообщения об ошибке
        СтандартнаяОбработка = Ложь;
        
        // Парсим адрес временного хранилища
        ПозицияАдреса = Найти(ТекстОшибки, ПрефиксОшибки) + СтрДлина(ПрефиксОшибки);
        АдресХранилища = Сред(ТекстОшибки, ПозицияАдреса);
        
        // Получаем структуру с ошибками на клиенте
        СтруктураОшибок = ПолучитьИзВременногоХранилища(АдресХранилища);
        
        // Открываем специальную форму отчета об ошибках проверки для пользователя
        ПараметрыОтображения = Новый Структура("Ошибки", СтруктураОшибок);
        ОткрытьФорму("ОбщаяФорма.ОтображениеОшибокПроверки", ПараметрыОтображения);
    КонецЕсли;
    
КонецПроцедуры

Скрытые риски и важные рекомендации

Используя первый способ (отложенный отказ), всегда помните о важных архитектурных нюансах:

← На главную