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

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

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

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

Почему нельзя использовать ОбработкаПроведения для вызова другого документа?

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

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

Золотое правило 1С: В процедуре ОбработкаПроведения должна находиться только логика формирования движений текущего документа. Все проверки и действия со сторонними объектами должны выноситься «за скобки».

Проблема отсутствия движений при программном проведении

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

Проанализируем причину. В типовых и самописных конфигурациях часто часть логики «размазана» между Модулем формы и Модулем объекта. Например, в событии формы ПриИзменении какого-либо реквизита может стоять флаг (булево значение), который сообщает модулю проведения, что данные были изменены и их нужно обработать.

Когда мы создаем документ программно через Документы.Название.СоздатьДокумент(), контекст формы не инициализируется. События ПриИзменении на форме не срабатывают. В результате важные служебные реквизиты (скрытые флаги) остаются незаполненными, и процедура проведения просто «пропускает» блоки кода, отвечающие за движения.

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


// В модуле объекта или внешней обработке
НовыйДок = Документы.КадровоеПеремещение.СоздатьДокумент();
НовыйДок.Дата = ТекущаяДата();
// Заполнение ТЧ
НоваяСтрока = НовыйДок.Отпуска.Добавить();
НоваяСтрока.ДниОтпуска = 0;

// Критически важный момент: 
// Устанавливаем служебный реквизит, который обычно взводится на форме
НовыйДок.ИзменитьЕжегодныеОтпуска = Истина; 

НовыйДок.Записать(РежимЗаписиДокумента.Проведение);

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


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

Решение проблемы через подписки на события

Наиболее правильным и современным способом проведения связанных документов является использование Подписок на события. Рассмотрим, как это работает по шагам:

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

Проанализируем ситуацию с рекурсией. Если документ А проводит документ Б, а в документе Б тоже стоит логика, которая может затронуть документ А, возникнет бесконечный цикл. Чтобы этого избежать, используйте коллекцию ДополнительныеСвойства.

Рассмотрим пример предотвращения зацикливания:


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

// В коде обработчика (подписке или модуле объекта)
Если Источник.ДополнительныеСвойства.Свойство("ЭтоПрограммноеПроведение") Тогда
    Возврат; // Выходим, чтобы не запускать логику повторно
КонецЕсли;

Использование фоновых заданий для высокой производительности

Если ваша база данных работает в многопользовательском режиме (клиент-сервер), лучшим вариантом будет асинхронное проведение. Выясним, как это реализовать:

В событии ПослеЗаписи основного документа мы не вызываем проведение второго документа напрямую. Вместо этого мы регистрируем задание в очереди. Это можно сделать через ФоновыеЗадания.Выполнить(). Таким образом, основной документ закроется и снимет все блокировки мгновенно, а второй документ будет проведен сервером в отдельном потоке через несколько секунд. Это исключает 99% проблем с блокировками (Deadlocks).

Итоги и рекомендации

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

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

← На главную