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