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