При разработке в среде «1С:Предприятие 8» на управляемых формах разработчики часто сталкиваются с фундаментальным поведением платформы: если в процессе записи объекта (в модуле объекта или в связанных подписках на события) переменная Отказ принимает значение Истина, то транзакция записи откатывается. Как следствие, обработчик формы ПослеЗаписиНаСервере() не выполняется вообще.
Это создает серьезную проблему, если в процессе записи выполнялись сложные проверки бизнес-логики, результаты которых были помещены в коллекцию ДополнительныеСвойства объекта. Разработчику необходимо получить эти данные (например, подробную структуру ошибок проведения, информацию о нехватке остатков, расхождениях в ценах) и красиво отобразить их пользователю в управляемой форме. Однако, поскольку транзакция прервана, стандартные механизмы передачи данных из объекта на форму не срабатывают.
В этой статье мы подробно разберем, почему так происходит, проанализируем архитектурную цепочку событий и рассмотрим несколько практических вариантов решения этой проблемы с примерами готового кода.
Давайте проанализируем стандартную последовательность вызова серверных обработчиков при интерактивной записи элемента справочника или документа из управляемой формы:
ПередЗаписьюНаСервере().ПередЗаписью().ПриЗаписи() (и обработка проведения для документов).ПриЗаписиНаСервере().ПослеЗаписиНаСервере().Если на шаге 3 или 4 взводится флаг Отказ = Истина, платформа немедленно инициирует откат транзакции СУБД. Все изменения стираются из базы данных, а выполнение серверной цепочки обрывается. Шаги 5, 6 и 7 просто игнорируются платформой, так как сам факт записи признан несостоявшимся.
Рассмотрим подробнее три основных способа обхода этого системного ограничения.
Это наиболее популярное и гибкое решение. Идея заключается в том, чтобы не устанавливать признак Отказ = Истина непосредственно в модуле объекта при интерактивной работе. Вместо этого мы передаем признак ошибки и все собранные диагностические данные через коллекцию ДополнительныеСвойства объекта на форму. Окончательное решение об отказе мы принимаем уже на стороне формы в обработчике ПриЗаписиНаСервере(), который гарантированно вызывается, так как на уровне объекта отказа не произошло.
Давайте пошагово реализуем этот алгоритм в коде.
Шаг 1. Модуль управляемой формы документа
В модуле формы мы инициализируем коллекцию дополнительных свойств и перехватываем отказ на этапе записи на сервере:
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Инициализируем свойства для передачи сигналов и структуры данных
ТекущийОбъект.ДополнительныеСвойства.Вставить("ИнтерактивнаяЗапись", Истина);
ТекущийОбъект.ДополнительныеСвойства.Вставить("ОтказПриЗаписи", Ложь);
ТекущийОбъект.ДополнительныеСвойства.Вставить("РезультатыАнализа", Новый Структура);
КонецПроцедуры
&НаСервере
Процедура ПриЗаписиНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Проверяем, зафиксировал ли модуль объекта критические ошибки проверок
Если ТекущийОбъект.ДополнительныеСвойства.Свойство("ОтказПриЗаписи")
И ТекущийОбъект.ДополнительныеСвойства.ОтказПриЗаписи Тогда
// Взводим отказ в форме. Транзакция будет отменена!
Отказ = Истина;
// Извлекаем собранные данные об ошибках и помещаем их в реквизит формы
Если ТекущийОбъект.ДополнительныеСвойства.Свойство("РезультатыАнализа") Тогда
// РезультатПроверки - это реквизит формы (например, типа Произвольный)
РезультатПроверки = ТекущийОбъект.ДополнительныеСвойства.РезультатыАнализа;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Шаг 2. Модуль объекта (документа или справочника)
В модуле объекта мы разделяем логику для программной записи (где формы нет и нужно сразу выдавать жесткий отказ) и интерактивной записи (где мы делегируем отказ форме):
Процедура ПриЗаписи(Отказ)
// Проводим наши сложные проверки бизнес-логики
СтруктураОшибок = Новый Структура;
ПроверкиУспешны = ВыполнитьСложныеПроверкиНаСервере(СтруктураОшибок);
Если Не ПроверкиУспешны Тогда
// Проверяем, ведется ли запись интерактивно из управляемой формы
Если ДополнительныеСвойства.Свойство("ИнтерактивнаяЗапись") Тогда
// Откладываем отказ, передавая флаг и данные в ДополнительныеСвойства
ДополнительныеСвойства.ОтказПриЗаписи = Истина;
ДополнительныеСвойства.РезультатыАнализа = СтруктураОшибок;
Иначе
// При программной записи (обработки, фоновые задания) сразу делаем жесткий отказ
Отказ = Истина;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Важное примечание для документов: Если ваш объект является документом и записывается в режиме проведения, нам обязательно нужно заблокировать выполнение стандартного обработчика проведения, чтобы система не пыталась формировать движения при наличии отложенного отказа:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Предотвращаем проведение, если в событии ПриЗаписи был зафиксирован отложенный отказ
Если ДополнительныеСвойства.Sвойство("ОтказПриЗаписи") И ДополнительныеСвойства.ОтказПриЗаписи Тогда
Отказ = Истина;
Возврат;
КонецЕсли;
// Стандартный алгоритм проведения...
КонецПроцедуры
Этот способ позволяет избежать усложнения логики в модуле объекта и переноса флагов отказа. Мы можем принудительно перехватить исключение при попытке записи непосредственно в модуле управляемой формы на клиенте.
Давайте разберем, как выглядит этот обходной путь:
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
// ФлагЗаписиПрограммной - это реквизит формы типа Булево (по умолчанию Ложь)
Если Не ФлагЗаписиПрограммной Тогда
// Отменяем стандартную интерактивную запись, чтобы выполнить её под нашим контролем
Отказ = Истина;
ФлагЗаписиПрограммной = Истина;
Попытка
// Запускаем запись программно в контролируемом блоке
Записать(ПараметрыЗаписи);
Исключение
// Сюда управление передается, если в модуле объекта произошел стандартный Отказ
// Мы можем обработать ошибки и вывести детальный отчет пользователю
ПоказатьДеталиОшибкиПользователю();
// Сбрасываем вспомогательный флаг формы
ФлагЗаписиПрограммной = Ложь;
КонецПопытки;
Иначе
// Сбрасываем флаг для возможности последующих интерактивных попыток записи
ФлагЗаписиПрограммной = Ложь;
КонецЕсли;
КонецПроцедуры
Этот подход более простой в реализации, однако он имеет ограничения: он полностью завязан на клиентский контекст формы и не позволяет изящно передать типизированные структуры данных из модуля объекта в форму без использования сериализации или временных хранилищ.
Если вы работаете на современных версиях платформы «1С:Предприятие 8.3» (начиная с 8.3.15), вы можете использовать передовой подход, соответствующий стандартам разработки БСП. Этот способ полностью безопасен и не требует искусственного переноса флага отказа.
Алгоритм действий выглядит следующим образом:
ОбработкаОтображенияОшибки() перехватываем эту ошибку, извлекаем данные по адресу хранилища и передаем их в форму для красивой отрисовки.Давайте посмотрим на программную реализацию этой схемы.
Код в модуле объекта:
Процедура ПриЗаписи(Отказ)
// Проверки провалились
Если Не ПроверкиПройдены Тогда
Отказ = Истина;
// Помещаем детальные данные во временное хранилище базы
АдресХранилища = ПоместитьВоВременноеХранилище(СтруктураДетальныхОшибок, Новый УникальныйИдентификатор);
// Вызываем исключение со специальным префиксом
ВызватьИсключение "ДЕТАЛЬНЫЙ_ОТКАЗ_ЗАПИСИ:" + АдресХранилища;
КонецЕсли;
КонецПроцедуры
Код в модуле управляемого приложения:
Процедура ОбработкаОтображенияОшибки(ИнформацияОбОшибке, СтандартнаяОбработка, ПереданныеПараметры)
ТекстОшибки = ИнформацияОбОшибке.Описание;
ПрефиксОшибки = "ДЕТАЛЬНЫЙ_ОТКАЗ_ЗАПИСИ:";
Если Найти(ТекстОшибки, ПрефиксОшибки) > 0 Тогда
// Отключаем стандартный вывод системного сообщения об ошибке
СтандартнаяОбработка = Ложь;
// Парсим адрес временного хранилища
ПозицияАдреса = Найти(ТекстОшибки, ПрефиксОшибки) + СтрДлина(ПрефиксОшибки);
АдресХранилища = Сред(ТекстОшибки, ПозицияАдреса);
// Получаем структуру с ошибками на клиенте
СтруктураОшибок = ПолучитьИзВременногоХранилища(АдресХранилища);
// Открываем специальную форму отчета об ошибках проверки для пользователя
ПараметрыОтображения = Новый Структура("Ошибки", СтруктураОшибок);
ОткрытьФорму("ОбщаяФорма.ОтображениеОшибокПроверки", ПараметрыОтображения);
КонецЕсли;
КонецПроцедуры
Используя первый способ (отложенный отказ), всегда помните о важных архитектурных нюансах:
ПриЗаписи) до обработчика формы ПриЗаписиНаСервере, транзакция СУБД остается открытой. Это может вызывать избыточные блокировки таблиц базы данных при высокой многопользовательской нагрузке.ДополнительныеСвойства). Если документ записывается программно (через Web-сервисы, фоновые задания или групповую обработку), код модуля объекта должен делать жесткий откат, иначе некорректные данные запишутся в базу в обход контроля.ПередЗаписью() управляемой формы на клиенте/сервере или через событие ОбработкаПроверкиЗаполнения(). Это сохранит высокую производительность вашей системы и избавит от проблем с «битыми» транзакциями БД.