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

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

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

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

Решение 1. Использование независимой записи в Журнал Регистрации (ЖР)

Самый простой и наименее ресурсоемкий способ зафиксировать ошибку в момент отмены проведения — это использовать стандартный механизм платформы для работы с журналом регистрации. Разберем по шагам, как это работает.

Функция ЗаписьЖурналаРегистрации() имеет специальный параметр РежимТранзакцииЗаписиЖурналаРегистрации. Если установить его в значение Независимая, платформа гарантированно сохранит эту запись в лог сразу же в момент вызова функции, даже если транзакция документа в итоге будет отменена.

Посмотрим на пример кода, который мы можем разместить в модуле объекта документа:


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

КонецПроцедуры

Что делать с этими записями дальше? Мы настраиваем отдельное регламентное задание, которое раз в 10-15 минут читает журнал регистрации с жестким отбором по событию Регистрация ошибок проведения. Найденные записи регламентное задание аккуратно переносит уже в наш рабочий регистр сведений. Это позволяет снять нагрузку с оперативного проведения и не замедлять работу пользователей.

Решение 2. Асинхронный запуск фонового задания

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

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

Пример вызова фонового задания из модуля объекта при отказе проведения:


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

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

Решение 3. HTTP-запрос к самому себе (Loopback)

Более современный и изящный способ выполнения внетранзакционных действий — использование HTTP-сервисов внутри самой конфигурации. Этот подход работает похожим образом, что и фоновое задание, но зачастую отрабатывает быстрее и с меньшими накладными расходами на инициализацию сеанса.

  1. Мы создаем в конфигурации собственный HTTP-сервис (например, ErrorHandler).
  2. В модуле проведения, если мы понимаем, что Отказ = Истина, мы формируем структуру с данными об ошибке (ссылка и текст).
  3. Сериализуем эту структуру в JSON.
  4. Используя объект HTTPСоединение, отправляем POST-запрос на адрес нашей же базы (localhost).

Запрос приходит на HTTP-сервис, который спокойно выполняет запись в регистр сведений в своей собственной транзакции. Откат исходного документа на него не влияет.

Решение 4. Перехват ошибок после транзакции (объект СообщениеПользователю)

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

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

Рассмотрим, что мы можем сделать в форме документа:


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

КонецПроцедуры

Для фонового и массового программного проведения документов из обработок мы можем использовать конструкцию Попытка ... Исключение с явной передачей параметра проведения:


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

Резюме: какое решение выбрать?

Мы проанализировали несколько надежных путей решения задачи сохранения данных при отмене транзакции. Выбор зависит от ваших условий:

  1. Если у вас огромный поток документов и высокая нагрузка на сервер — используйте асинхронную запись в журнал регистрации (режим Независимая) с последующим парсингом регламентным заданием. Это самый безопасный для базы вариант.
  2. Если записей немного и нужна мгновенная реакция (например, в рознице или при сложных интеграциях) — используйте HTTP-запросы к самому себе или ФоновыеЗадания.Выполнить().
  3. Если проблема актуальна только для интерактивной работы пользователей — перехватывайте стек ошибок в событии формы ПослеЗаписиНаСервере.
← На главную