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