При разработке в 1С:Предприятие мы часто сталкиваемся с задачей зафиксировать причины, по которым документ не удалось провести. Например, нам нужно записать информацию об ошибке проверки заполнения или нехватке остатков в специальный служебный регистр сведений. Однако мы сталкиваемся с жестким архитектурным ограничением платформы: если в обработчике ОбработкаПроведения или ПередЗаписью устанавливается флаг Отказ = Истина, платформа полностью откатывает текущую транзакцию.
Из-за отката транзакции любые изменения в базе данных, которые мы попытались сделать в процессе проведения (включая запись в наш независимый регистр сведений), будут безвозвратно удалены. Рассмотрим подробнее, как обойти это ограничение и какие методы внетранзакционной записи существуют в современной разработке на 1С.
Самый простой и наименее ресурсоемкий способ зафиксировать ошибку в момент отмены проведения — это использовать стандартный механизм платформы для работы с журналом регистрации. Разберем по шагам, как это работает.
Функция ЗаписьЖурналаРегистрации() имеет специальный параметр РежимТранзакцииЗаписиЖурналаРегистрации. Если установить его в значение Независимая, платформа гарантированно сохранит эту запись в лог сразу же в момент вызова функции, даже если транзакция документа в итоге будет отменена.
Посмотрим на пример кода, который мы можем разместить в модуле объекта документа:
Процедура ОбработкаПроведения(Отказ, Режим)
// Какая-то логика проверок...
Если ОшибкаПроверки Тогда
Отказ = Истина;
ТекстОшибки = "Не хватает остатков по номенклатуре";
ЗаписьЖурналаРегистрации(
"Регистрация ошибок проведения",
УровеньЖурналаРегистрации.Информация,
Метаданные(),
Ссылка,
ТекстОшибки,
РежимТранзакцииЗаписиЖурналаРегистрации.Независимая
);
КонецЕсли;
КонецПроцедуры
Что делать с этими записями дальше? Мы настраиваем отдельное регламентное задание, которое раз в 10-15 минут читает журнал регистрации с жестким отбором по событию Регистрация ошибок проведения. Найденные записи регламентное задание аккуратно переносит уже в наш рабочий регистр сведений. Это позволяет снять нагрузку с оперативного проведения и не замедлять работу пользователей.
Если нам нужно, чтобы запись в регистр сведений появлялась мгновенно, мы можем инициировать параллельный сеанс без привязки к текущей отменяемой транзакции. Для этого отлично подходят фоновые задания.
Проанализируем ситуацию: когда мы вызываем фоновое задание через программный код, платформа открывает новый сеанс на сервере. В этом новом сеансе открывается своя собственная независимая транзакция, на которую никак не влияет откат нашей текущей транзакции проведения документа.
Пример вызова фонового задания из модуля объекта при отказе проведения:
Если Отказ = Истина Тогда
ПараметрыВыполнения = Новый Массив;
ПараметрыВыполнения.Добавить(Ссылка); // Передаем ссылку на проблемный документ
ПараметрыВыполнения.Добавить("Описание причины отказа");
УИФоновогоЗадания = Новый УникальныйИдентификатор;
ФоновыеЗадания.Выполнить(
"ЛогированиеСервер.ЗарегистрироватьОшибкуПроведения",
ПараметрыВыполнения,
УИФоновогоЗадания
);
КонецЕсли;
Важно: этот метод создает определенную нагрузку на сервер 1С. Если у вас происходит массовое перепроведение тысяч документов и все они падают с ошибками, создание тысяч фоновых заданий может исчерпать ресурсы рабочего процесса.
Более современный и изящный способ выполнения внетранзакционных действий — использование HTTP-сервисов внутри самой конфигурации. Этот подход работает похожим образом, что и фоновое задание, но зачастую отрабатывает быстрее и с меньшими накладными расходами на инициализацию сеанса.
ErrorHandler).Отказ = Истина, мы формируем структуру с данными об ошибке (ссылка и текст).HTTPСоединение, отправляем POST-запрос на адрес нашей же базы (localhost).Запрос приходит на HTTP-сервис, который спокойно выполняет запись в регистр сведений в своей собственной транзакции. Откат исходного документа на него не влияет.
Если мы говорим об интерактивном проведении документа пользователем из формы, выясним причину, как можно обойтись без фоновых заданий. В этом нам поможет правильное понимание порядка возникновения событий на форме.
Когда пользователь нажимает "Провести", начинается серверный вызов. Если транзакция отменяется, формируются объекты СообщениеПользователю. После того как транзакция отменена, управление возвращается в модуль формы в событие ПослеЗаписиНаСервере или клиентское ПослеЗаписи. В этот момент блокировка базы данных уже снята, и транзакции нет.
Рассмотрим, что мы можем сделать в форме документа:
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
// Получаем массив всех сообщений, которые были сгенерированы при попытке записи
МассивСообщений = ПолучитьСообщенияПользователю(Истина);
Если МассивСообщений.Количество() > 0 Тогда
Для Каждого Сообщение Из МассивСообщений Цикл
// Если документ не провелся, пишем текст сообщения в регистр
ЗаписатьОшибкуВРегистрСведений(ТекущийОбъект.Ссылка, Сообщение.Текст);
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Для фонового и массового программного проведения документов из обработок мы можем использовать конструкцию Попытка ... Исключение с явной передачей параметра проведения:
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ТекстОшибки = ОписаниеОшибки();
// Транзакция проведения уже отменена, мы можем смело писать в регистр
ЗаписатьОшибкуВРегистрСведений(ДокументОбъект.Ссылка, ТекстОшибки);
КонецПопытки;
Мы проанализировали несколько надежных путей решения задачи сохранения данных при отмене транзакции. Выбор зависит от ваших условий:
Независимая) с последующим парсингом регламентным заданием. Это самый безопасный для базы вариант.ФоновыеЗадания.Выполнить().ПослеЗаписиНаСервере.