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