В стандартной работе платформы «1С:Предприятие 8» процесс перепроведения уже проведенного документа выполняется в рамках одной неделимой транзакции. Платформа делает это намеренно, чтобы обеспечить согласованность данных (атомарность операции): сначала очищаются старые движения документа, затем выполняется процедура ОбработкаПроведения. Если на этапе проведения возникает ошибка (например, не хватило остатков на складе или сработала жесткая бизнес-проверка), вся транзакция полностью откатывается. В результате документ возвращается в свое исходное, успешно проведенное состояние со всеми старыми движениями.
Однако в реальной практике разработки иногда возникает иная задача: необходимо принудительно разделить этот процесс на две независимые транзакции. При этом бизнес-логика требует следующего поведения:
В рамках этой статьи мы подробно разберем, почему стандартные подписки на события не помогут решить эту задачу, детально проанализируем механизм «обреченных транзакций» и рассмотрим три надежных способа реализации этой логики на практике.
Проанализируем распространенную ошибку начинающих разработчиков. Часто возникает идея перехватить проведение внутри подписок на события ПередЗаписью или ПриЗаписи, используя свойство объекта ДополнительныеСвойства для управления ветками кода или пытаясь обернуть вызовы в блоки Попытка...Исключение.
Выясним причину, почему этот подход принципиально не работает в среде 1С. В платформе «1С:Предприятие» реализован механизм «обреченных транзакций» (Doomed Transactions):
НачатьТранзакцию() внутри уже открытой транзакции (например, внутри обработчиков записи объекта), платформа просто увеличивает внутренний счетчик транзакций.Попытка...Исключение внутри подписки на событие, платформа в конце выполнения все равно принудительно откатит всю транзакцию записи целиком. Изменения первого шага (отмена проведения) не зафиксируются.Таким образом, разделить процесс на две транзакции можно только из внешнего контекста, который находится абсолютно вне транзакции записи самого документа.
Рассмотрим самый распространенный и надежный способ программного разделения. Данный код должен выполняться в контексте, где на момент старта нет открытых транзакций (например, во внешней обработке, регламентном задании, групповой обработке или в модуле менеджера при вызове из непривязанных процедур).
Разберем процесс по шагам. Мы выполним два последовательных и полностью независимых вызова метода Записать().
// Предположим, у нас есть ссылка на документ в переменной "СсылкаНаДокумент"
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
// Шаг 1: Первая транзакция — отмена проведения.
// Этот шаг должен выполниться и жестко зафиксироваться в базе данных.
Попытка
// Используем режим автозаписи для отмены проведения
ДокументОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения);
// На этом этапе документ гарантированно стал непроведенным,
// его движения в регистрах полностью очищены, а транзакция закрыта.
Исключение
// Обрабатываем непредвиденную ошибку, если даже отменить проведение не удалось
ЗаписатьЛогОшибки("Не удалось отменить проведение документа: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение; // Прерываем процесс
КонецПопытки;
// Шаг 2: Вторая транзакция — попытка проведения документа.
Попытка
// Пытаемся провести документ заново
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// Если проведение завершилось ошибкой, транзакция проведения откатывается.
// Но первая транзакция (отмена проведения) уже зафиксирована!
// Документ остается в базе данных в статусе "Не проведен".
ЗаписатьЛогОшибки("Документ записан как непроведенный. Ошибка проведения: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Важное улучшение для Шага 1: В реальных конфигурациях при отмене проведения могут срабатывать жесткие прикладные проверки (например, проверка заполненности реквизитов, даты запрета изменения данных и т.д.). Чтобы гарантировать успешное выполнение первой транзакции и безусловную очистку движений, мы можем временно задействовать режим загрузки данных. Посмотрим на пример кода:
// Включаем режим загрузки для отключения прикладного кода проверок на Шаге 1
ДокументОбъект.ОбменДанными.Загрузка = Истина;
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения);
Исключение
// Обработка критической ошибки
КонецПопытки;
// Обязательно возвращаем режим загрузки в Ложь перед вторым шагом!
ДокументОбъект.ОбменДанными.Загрузка = Ложь;
// Шаг 2: Обычное проведение со всеми проверками и бизнес-логикой
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// Документ остался непроведенным
КонецПопытки;
Проанализируем ситуацию, когда такое поведение требуется при непосредственной работе пользователя в управляемой форме документа. Если пользователь нажимает стандартную кнопку «Провести», платформа по умолчанию отрабатывает единую транзакцию.
Чтобы обойти это ограничение, выполним следующие действия по шагам:
ЗаписатьИПровестиДвеТранзакции и выведем ее на командную панель в виде кнопки «Провести».Посмотрим на пример программной реализации логики в модуле управляемой формы:
&НаКлиенте
Процедура ЗаписатьИПровестиДвеТранзакции(Команда)
// Очищаем сообщения, чтобы пользователь видел только актуальные ошибки
ОчиститьСообщения();
// Вызываем первый серверный метод для гарантированной отмены проведения
РезультатОтмены = ОтменитьПроведениеНаСервере();
Если Не РезультатОтмены Тогда
ПоказатьПредупреждение(, "Не удалось отменить проведение документа. Операция прервана.");
Возврат;
КонецЕсли;
// Документ успешно разпроведен. Теперь обновляем форму на клиенте.
Прочитать() ;
// Вызываем второй серверный метод для попытки проведения
РезультатПроведения = ПровестиНаСервере();
Если РезультатПроведения Тогда
ПоказатьОповещениеПользователя("Успешно", , "Документ успешно проведен!");
Иначе
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Документ не проведен из-за ошибок, но остался записанным как непроведенный.");
КонецЕсли;
// Снова обновляем форму, чтобы отобразить актуальный статус проведения и новые движения
Прочитать();
КонецПроцедуры
&НаСервере
Функция ОтменитьПроведениеНаСервере()
ДокументОбъект = РеквизитФормыВЗначение("Объект");
Попытка
// Отменяем проведение
ДокументОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения);
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
Возврат Истина;
Исключение
Возврат Ложь;
КонецПопытки;
КонецФункции
&НаСервере
Функция ПровестиНаСервере()
ДокументОбъект = РеквизитФормыВЗначение("Объект");
Попытка
// Пытаемся провести
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
Возврат Истина;
Исключение
// Исключение поймано. Объект формы теперь содержит измененные данные,
// но документ в базе данных не проведен.
Возврат Ложь;
КонецПопытки;
КонецФункции
Бывают сложные архитектурные сценарии, когда действие запускается из кода, который уже гарантированно выполняется в транзакции. Например, при проведении «Документа А» (например, «Закрытие месяца») нам требуется перепроводить «Документ Б» (например, «Требование-накладная»), причем с условием, что если «Документ Б» не проведется, он должен остаться непроведенным, но проведение «Документа А» при этом прерываться не должно.
В этом случае простая конструкция Попытка...Исключение внутри кода проведения «Документа А» не спасет от общего отката СУБД из-за описанного выше эффекта «обреченных транзакций». Решением является изоляция транзакционного контекста через Фоновые задания.
Проанализируем схему реализации этого подхода:
ФоновыеЗадания.Выполнить().Проектируя подобные решения, разработчик обязательно должен учитывать методологические риски для ведения учета в системе.
Главный риск: если вторая транзакция (проведение) завершается ошибкой, документ гарантированно остается в базе данных в статусе «Не проведен». Это означает, что все его движения по регистрам сведений, накопления и бухгалтерии полностью стираются.
В типовых конфигурациях 1С (таких как ERP, КА, УТ, БП) это может привести к лавинообразным ошибкам в учете:
Именно по этой причине стандартная логика платформы 1С всегда ориентирована на сохранение проведения документа в его старом виде в случае возникновения ошибок проведения.