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

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

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

Проблема изоляции транзакций и видимости данных

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

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

Решение 1: Явная запись движений внутри транзакции

Самый простой и быстрый способ решить проблему видимости — вызвать метод Записать() у коллекции движений первого документа до того, как начнется создание второго. Рассмотрим пример кода, который позволяет сделать движения «видимыми» для запросов внутри того же самого сеанса (транзакции):


Процедура ОбработкаПроведения(Отказ, РежимПроведения)

    // 1. Формируем движения текущего документа (Документ1)
    СтрокаДвижения = Движения.ОстаткиТоваров.Добавить();
    СтрокаДвижения.Период = Дата;
    СтрокаДвижения.Количество = 10;
    // ... заполнение остальных полей

    // 2. Явно записываем движения в базу данных, не дожидаясь окончания процедуры
    // Это не завершает транзакцию, но обновляет таблицы в контексте текущего сеанса
    Движения.ОстаткиТоваров.Записать();

    // 3. Создаем и проводим зависимый документ (Документ2)
    НовыйДок = Документы.Документ2.СоздатьДокумент();
    НовыйДок.Дата = Дата;
    // ... заполнение реквизитов Документа2
    
    Попытка
        НовыйДок.Записать(РежимЗаписиДокумента.Проведение);
    Исключение
        Сообщить("Ошибка при проведении зависимого документа: " + ОписаниеОшибки());
        Отказ = Истина;
    КонецПопытки;

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

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

Решение 2: Использование ДополнительныеСвойства и события ПослеЗаписи

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

  1. В процедуре ОбработкаПроведения установим флаг необходимости создания документа.
  2. В модуле формы или в подписке на событие ПослеЗаписи проверим этот флаг и инициируем создание.

Пример в модуле объекта Документа1:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    // Основная логика проведения
    // ...
    ДополнительныеСвойства.Вставить("НужноСоздатьДокумент2", Истина);
КонецПроцедуры

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

Решение 3: Использование фоновых заданий (Архитектурно верный подход)

В высоконагруженных системах создание цепочек документов внутри одной транзакции может привести к взаимоблокировкам (Deadlocks). Проанализируем ситуацию: Документ1 заблокировал регистр «А», затем начал проводить Документ2, который пытается заблокировать регистр «Б». В это время другой пользователь делает обратное. Чтобы этого избежать, создание зависимых объектов выносят в оперативные фоновые задания.

Рассмотрим, как это работает:

Чего делать категорически нельзя

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


// ПЛОХОЙ ПРИМЕР
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    НачатьТранзакцию(); // ОШИБКА: Транзакция уже открыта платформой
    // ... код
    ЗафиксироватьТранзакцию(); // ОШИБКА: Попытка принудительной фиксации нарушит логику платформы
КонецПроцедуры

Попытка вызвать ЗафиксироватьТранзакцию() внутри ОбработкаПроведения приведет к исключительной ситуации, так как количество вызовов НачатьТранзакцию и ЗафиксироватьТранзакцию должно быть сбалансировано, а системная транзакция управляется самой платформой.

Рекомендации по безопасности и стабильности

При программном создании зависимых документов придерживайтесь следующих правил:

  1. Проверка на повторное создание: Всегда проверяйте, не был ли Документ2 создан ранее. Используйте реквизит-ссылку в Документе1 для хранения связи.
  2. ОбменДанными.Загрузка: Если вы полностью уверены в данных, которые передаете в Документ2, устанавливайте НовыйДок.ОбменДанными.Загрузка = Истина. Это отключит лишние проверки и ускорит проведение, но помните, что в этом случае ответственность за корректность данных лежит полностью на вашем коде.
  3. Обработка ошибок: Всегда оборачивайте вызов Записать(РежимЗаписиДокумента.Проведение) в блок Попытка...Исключение, чтобы ошибка в зависимом документе не «схлопнула» систему с невнятным сообщением, а была корректно обработана.

Таким образом, для решения задачи «видимости» движений наиболее эффективным является метод Движения.ИмяРегистра.Записать(), а для обеспечения масштабируемости системы — вынос логики в события после фиксации транзакции или фоновые задания.

← На главную