Рассмотрим частую задачу, с которой сталкиваются разработчики 1С: при проведении первичного документа (например, «Приобретение товаров и услуг») необходимо программно создать и провести подчиненный документ другого вида (например, «Заявка на расходование денежных средств»).
На первый взгляд, решение кажется простым — достаточно добавить в обработчик проведения исходного документа следующий код:
НовДокумент = Документы.ЗаявкаНаРасходованиеДенежныхСредств.СоздатьДокумент();
НовДокумент.Заполнить(ЭтотОбъект.Ссылка);
НовДокумент.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Оперативный);
Если подчиненный документ успешно проводится, всё работает корректно. Но проанализируем ситуацию, когда заявка не может быть проведена по бизнес-логике (например, превышен лимит денежных средств). Возникает системная ошибка, и прерывается проведение обоих документов.
Если мы попытаемся обернуть вызов в конструкцию Попытка ... Исключение, то столкнемся с критической ошибкой платформы: «В данной транзакции происходили ошибки». Выясним причину такого поведения и разберем правильные способы решения этой задачи.
Разберем механику работы транзакций в платформе 1С. При проведении любого документа платформа неявно открывает главную транзакцию базы данных. Когда внутри обработчика проведения вызывается метод Записать с режимом проведения для другого документа, платформа открывает вложенную транзакцию.
Главная архитектурная особенность СУБД в связке с 1С заключается в том, что если во вложенной транзакции происходит отказ (документ не провелся из-за нехватки лимитов, вызова исключения или отказа в подписке на событие), вся глобальная транзакция помечается как невосстановимая. Несмотря на то, что блок Попытка ... Исключение перехватывает ошибку выполнения кода, в момент попытки зафиксировать основную транзакцию (завершить проведение первого документа) платформа неминуемо выполнит откат (rollback).
Попытка провести один документ внутри модуля проведения другого считается плохой практикой (так называемым «г..кодом»), поскольку приводит к непредсказуемым блокировкам и нарушает принципы изоляции транзакций. Посмотрим на правильные архитектурные подходы к решению этой проблемы.
Вместо того чтобы надеяться на возникновение ошибки при проведении подчиненного документа, мы можем и должны самостоятельно валидировать данные до инициации записи.
Разберем по шагам данный подход:
Записать для второго документа мы программно проверяем все условия, которые могут вызвать отказ (например, выполняем запрос к регистрам для проверки остатка лимитов по статьям ДДС).РежимЗаписиДокумента.Запись.Таким образом, мы избегаем открытия вложенной транзакции проведения, которая заведомо завершится отказом, и основной документ успешно проводится, а заявка остается в статусе черновика для ручной корректировки пользователем.
Проанализируем архитектурный стандарт, который применяется в современных типовых конфигурациях (ERP, Комплексная автоматизация, Управление торговлей) на базе Библиотеки стандартных подсистем (БСП). Этот подход полностью развязывает транзакции во времени и избавляет систему от взаимных блокировок при массовом перепроведении.
Создадим механизм отложенной обработки:
ОчередьСозданияЗаявокНаОплату. В измерениях указываем ссылку на первичный документ (ДокументОснование).Важное замечание: перед созданием фонового задания имеет смысл прочитать данные СУБД и оценить, требуется ли вообще создание заявки для данного документа-основания. Это позволит избежать запуска лишних фоновых процессов при массовом перепроведении (исключит так называемые «ложные срабатывания»).
Если бизнес-процесс подразумевает, что первичный документ всегда проводится только интерактивно пользователем (из формы документа), мы можем вынести логику за пределы транзакции объекта.
Рассмотрим использование события ПослеЗаписиНаСервере модуля формы. Это событие вызывается платформой уже после того, как основная транзакция записи и проведения успешно зафиксирована в базе данных. Следовательно, любые ошибки, попытки и исключения при создании второго документа здесь никак не повлияют на проведенный статус первого документа.
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
Если ПараметрыЗаписи.РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
СоздатьИПровестиЗаявку(ТекущийОбъект.Ссылка);
КонецЕсли;
КонецПроцедуры
Важно: этот способ имеет существенное ограничение. Код в модуле формы не будет выполнен при программном перепроведении документов, при групповом перепроведении из списков или при обмене данными. Использовать его следует только с четким пониманием этих ограничений.
Подводя итоги, при необходимости программного ввода на основании лучшей практикой является использование архитектуры отложенного проведения с применением регистра сведений в качестве очереди и регламентных заданий в качестве исполнителей. Это гарантирует стабильную работу системы без отказов транзакций и мертвых блокировок.