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

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

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

Однако в реальной практике разработки иногда возникает иная задача: необходимо принудительно разделить этот процесс на две независимые транзакции. При этом бизнес-логика требует следующего поведения:

  1. Первая транзакция должна полностью отменить проведение документа, зафиксировав это состояние в базе данных (движения удаляются, статус меняется на «Не проведен»).
  2. Вторая транзакция пытается провести документ заново. Если во время проведения возникает исключительная ситуация, документ должен остаться в базе данных в статусе «Не проведен», а не возвращаться к старому проведенному состоянию.

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

Почему нельзя решить задачу внутри стандартных событий (ПередЗаписью, ОбработкаПроведения)?

Проанализируем распространенную ошибку начинающих разработчиков. Часто возникает идея перехватить проведение внутри подписок на события ПередЗаписью или ПриЗаписи, используя свойство объекта ДополнительныеСвойства для управления ветками кода или пытаясь обернуть вызовы в блоки Попытка...Исключение.

Выясним причину, почему этот подход принципиально не работает в среде 1С. В платформе «1С:Предприятие» реализован механизм «обреченных транзакций» (Doomed Transactions):

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

Решение 1. Программная реализация через последовательные транзакции

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

Разберем процесс по шагам. Мы выполним два последовательных и полностью независимых вызова метода Записать().


// Предположим, у нас есть ссылка на документ в переменной "СсылкаНаДокумент"
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();

// Шаг 1: Первая транзакция — отмена проведения.
// Этот шаг должен выполниться и жестко зафиксироваться в базе данных.
Попытка
    // Используем режим автозаписи для отмены проведения
    ДокументОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения);
    
    // На этом этапе документ гарантированно стал непроведенным, 
    // его движения в регистрах полностью очищены, а транзакция закрыта.
Исключение
    // Обрабатываем непредвиденную ошибку, если даже отменить проведение не удалось
    ЗаписатьЛогОшибки("Не удалось отменить проведение документа: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
    ВызватьИсключение; // Прерываем процесс
КонецПопытки;

// Шаг 2: Вторая транзакция — попытка проведения документа.
Попытка
    // Пытаемся провести документ заново
    ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
    // Если проведение завершилось ошибкой, транзакция проведения откатывается.
    // Но первая транзакция (отмена проведения) уже зафиксирована!
    // Документ остается в базе данных в статусе "Не проведен".
    ЗаписатьЛогОшибки("Документ записан как непроведенный. Ошибка проведения: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

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


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

// Обязательно возвращаем режим загрузки в Ложь перед вторым шагом!
ДокументОбъект.ОбменДанными.Загрузка = Ложь;

// Шаг 2: Обычное проведение со всеми проверками и бизнес-логикой
Попытка
    ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
    // Документ остался непроведенным
КонецПопытки;

Решение 2. Реализация на уровне управляемой формы (Интерфейсный вариант)

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

Чтобы обойти это ограничение, выполним следующие действия по шагам:

  1. В свойствах управляемой формы документа отключим стандартные команды записи и проведения (или просто скроем стандартные кнопки «Провести» и «Провести и закрыть»).
  2. Создадим на форме свою собственную команду ЗаписатьИПровестиДвеТранзакции и выведем ее на командную панель в виде кнопки «Провести».
  3. Напишем обработчик этой команды, который последовательно вызывает серверные методы без общей транзакции.

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


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

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

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

Решение 3. Изоляция транзакций с помощью Фоновых заданий

Бывают сложные архитектурные сценарии, когда действие запускается из кода, который уже гарантированно выполняется в транзакции. Например, при проведении «Документа А» (например, «Закрытие месяца») нам требуется перепроводить «Документ Б» (например, «Требование-накладная»), причем с условием, что если «Документ Б» не проведется, он должен остаться непроведенным, но проведение «Документа А» при этом прерываться не должно.

В этом случае простая конструкция Попытка...Исключение внутри кода проведения «Документа А» не спасет от общего отката СУБД из-за описанного выше эффекта «обреченных транзакций». Решением является изоляция транзакционного контекста через Фоновые задания.

Проанализируем схему реализации этого подхода:

  1. Основной транзакционный код регистрирует необходимость перепроведения целевого документа. Для этого можно использовать временный регистр сведений (например, «ОчередьОтложенногоПерепроведения») или передавать параметры напрямую в фоновое задание.
  2. Программа запускает новое фоновое задание, используя метод ФоновыеЗадания.Выполнить().
  3. Фоновое задание запускается платформой в отдельном сеансе на сервере «1С:Предприятия». Оно обладает абсолютно чистым, изолированным транзакционным контекстом.
  4. Код внутри фонового задания последовательно выполняет первую транзакцию (отмена проведения) и вторую транзакцию (проведение). Любые сбои и ошибки СУБД внутри фонового задания никак не повлияют на стабильность и выполнение транзакции основного документа.

Важный методологический риск разделения транзакций

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

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

В типовых конфигурациях 1С (таких как ERP, КА, УТ, БП) это может привести к лавинообразным ошибкам в учете:

Именно по этой причине стандартная логика платформы 1С всегда ориентирована на сохранение проведения документа в его старом виде в случае возникновения ошибок проведения.

← На главную