Как сохранить данные в 1С при отмене транзакции записи документа?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

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

Метод 1. Использование фоновых заданий

Это один из самых популярных и технически изящных способов. Когда мы запускаем фоновое задание, оно выполняется в отдельном сеансе на сервере 1С. У этого сеанса своя собственная транзакция, которая никак не связана с транзакцией основного процесса, вызвавшего Отказ = Истина.

Проанализируем алгоритм реализации: в процедуре ПриЗаписи (или ОбработкаПроведения), когда мы понимаем, что условия не выполнены, мы подготавливаем структуру с данными для логирования и передаем её в фоновое задание.

Пример кода для модуля объекта:


Процедура ПриЗаписи(Отказ)
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    // Проверяем наше условие (например, превышение веса доставки)
    Если ВесДоставки > МаксимальныйВес Тогда
        
        // Подготавливаем данные для записи в регистр
        ПараметрыЛога = Новый Структура;
        ПараметрыЛога.Вставить("ДокументСсылка", ЭтотОбъект.Ссылка);
        ПараметрыЛога.Вставить("Причина", "Превышение веса: " + Строка(ВесДоставки));
        ПараметрыЛога.Вставить("Период", ТекущаяДата());

        // Запускаем фоновое задание
        ФоновыеЗадания.Выполнить("МойОбщийМодуль.ЗаписатьОтказВРегистр", ПараметрыЛога);

        // Отменяем запись документа
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Заказ не может быть принят: превышен лимит веса!";
        Сообщение.Сообщить();
        Отказ = Истина;
    КонецЕсли;
КонецПроцедуры

Важно помнить: фоновое задание не увидит изменений документа, которые еще не зафиксированы в базе. Поэтому все необходимые данные (номер, сумма, причина) нужно передавать в параметрах ПараметрыЛога. В общем модуле МойОбщийМодуль процедура ЗаписатьОтказВРегистр должна быть экспортной и выполнять обычную запись в независимый регистр сведений.

Метод 2. Логирование через Журнал регистрации

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

Разберем по шагам, как превратить это в полноценный отчет:

  1. В момент отказа вызываем ЗаписатьВЖурналРегистрации, передавая в параметре "Данные" структуру или строку JSON с деталями ошибки.
  2. Создаем регламентное задание, которое раз в несколько минут анализирует новые записи в Журнале регистрации по определенному событию.
  3. Это задание программно переносит накопленные данные из журнала в нужный нам регистр сведений для удобного построения отчетов.

Этот метод гарантирует 100% сохранение информации даже при критических сбоях сервера БД, так как Журнал регистрации — это файлы на диске сервера 1С (или отдельная таблица, управляемая самим процессом 1С, а не транзакцией документа).

Метод 3. Использование HTTP-сервисов (Self-call)

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

В коде документа мы выполняем HTTP-запрос к своей же базе. Поскольку HTTP-запрос — это новый сеанс, он создаст свою транзакцию, зафиксирует запись в регистре и вернет управление основному коду, который затем спокойно выполнит Отказ = Истина.

Преимущества этого подхода:

Метод 4. Архитектурный обход через статусы документа

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

Рассмотрим этот подход подробнее:

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

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


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

Метод 5. Прямое соединение (ADO/SQL) или Внешние источники данных

Для SQL-версий 1С существует "хакерский", но эффективный метод. Можно открыть новое соединение с базой данных через ADO (ActiveX Data Objects) и выполнить прямой SQL-запрос INSERT в таблицу нужного регистра сведений. Поскольку это соединение устанавливается напрямую к SQL-серверу мимо менеджера транзакций 1С, данные будут зафиксированы моментально.

Однако мы должны использовать этот метод с большой осторожностью: нужно точно знать имя таблицы в SQL (оно может измениться после реструктуризации) и соблюдать структуру полей (UUID и прочее). В современных версиях платформы 8.3 для этих целей лучше использовать механизм ВнешниеИсточникиДанных, настроенный на ту же самую базу.

Резюме

Выясним причину, по которой стоит выбрать тот или иной метод:

Каждый из этих методов позволяет обойти ограничение транзакционности и обеспечить бизнес-пользователей необходимой аналитикой по отказам в системе.

← На главную