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