При разработке учетных механизмов и печатных форм часто возникает необходимость получить дерево или плоский список всех документов, которые были введены на основании конкретного документа (например, найти все счета-фактуры, приходные кассовые ордера, акты или корректировки, подчиненные документу РеализацияТоваровУслуг).
При попытке использовать встроенный метод КритерииОтбора.СвязанныеДокументы.Найти(Ссылка) разработчики нередко сталкиваются с ситуацией, когда метод возвращает только сам исходный документ либо только прямые связи первого уровня. Разберем детально, почему это происходит и как правильно организовать программный сбор всех подчиненных документов «вниз» по иерархии в конфигурациях «1С:Управление торговлей 11», «1С:Комплексная автоматизация» и «1С:ERP Управление предприятием».
Проанализируем механику работы критериев отбора в платформе 1С:Предприятие 8:
КритерииОтбора.СвязанныеДокументы.Найти() выполняет выборку только тех объектов, в реквизитах которых непосредственно записана переданная ссылка. Всю иерархию ниже первого уровня платформа автоматически не разворачивает.ДокументОснование, ПервичныйДокумент или строках табличных частей) содержится ссылка на самого себя или циклические связи цепочки.В типовых конфигурациях на базе БСП («УТ 11», «КА 2», «ERP 2») дерево структуры подчиненности строится программно. Логика формирования запросов сосредоточена в модулях подсистемы структуры подчиненности:
СвязанныеДокументы (ее модуль формы и вызовы серверных процедур).СтруктураПодчиненностиСлужебный и переопределяемый модуль СтруктураПодчиненностиПереопределяемый.В этих модулях на основе метаданных Метаданные.КритерииОтбора.СвязанныеДокументы.Состав строится динамический запрос с условиями вида ДокументОснование = &ЗначениеКритерияОтбора или ПервичныйДокумент = &ЗначениеКритерияОтбора.
Рассмотрим, как можно самостоятельно построить динамический запрос по всем объектам, входящим в состав критерия отбора СвязанныеДокументы. Этот подход позволяет получить все подчиненные документы первого уровня для переданной ссылки.
Функция ПолучитьПрямыеПодчиненныеДокументы(ЗначениеКритерияОтбора) Экспорт
ТипЗначениеКритерия = ТипЗнч(ЗначениеКритерияОтбора);
ТекстыЗапросов = Новый Массив;
Для Каждого ЭлементСостава Из Метаданные.КритерииОтбора.СвязанныеДокументы.Состав Цикл
// Проверяем, может ли данный реквизит хранить тип нашего документа
Если Не ЭлементСостава.Тип.СодержитТип(ТипЗначениеКритерия) Тогда
Продолжить;
КонецЕсли;
ПутьКДанным = ЭлементСостава.ПолноеИмя();
СтруктураПути = ОбщегоНазначения.РазобратьПутьКОбъектуМетаданных(ПутьКДанным);
// Проверяем права доступа на чтение объекта
Попытка
Если Не ПравоДоступа("Чтение", СтруктураПути.Метаданные) Тогда
Продолжить;
КонецЕсли;
Исключение
Продолжить;
КонецПопытки;
ИмяТаблицы = СтруктураПути.ТипОбъекта + "." + СтруктураПути.ВидОбъекта;
Если ЗначениеЗаполнено(СтруктураПути.ИмяТаблЧасти) Тогда
ИмяТаблицы = ИмяТаблицы + "." + СтруктураПути.ИмяТаблЧасти;
КонецЕсли;
ИмяПоля = СтруктураПути.ИмяРеквизита;
ТекстЗапросаЭлемента =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| ТекТаблица.Ссылка КАК Ссылка
|ИЗ
| " + ИмяТаблицы + " КАК ТекТаблица
|ГДЕ
| ТекТаблица." + ИмяПоля + " = &ЗначениеКритерияОтбора";
ТекстыЗапросов.Добавить(ТекстЗапросаЭлемента);
КонецЦикла;
Если ТекстыЗапросов.Количество() = 0 Тогда
Возврат Новый Массив;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = СтрСоединить(ТекстыЗапросов, Символы.ПС + "ОБЪЕДИНИТЬ" + Символы.ПС);
Запрос.УстановитьПараметр("ЗначениеКритерияОтбора", ЗначениеКритерияОтбора);
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
Возврат ТаблицаРезультата.ВыгрузитьКолонку("Ссылка");
КонецФункции
Поскольку подчиненные документы могут, в свою очередь, порождать следующие документы (например: ЗаказКлиента → РеализацияТоваровУслуг → СчетФактураВыданный), для поиска всех веток вниз требуется обход цепочек.
Важно: в реальных базах данных возможны циклические ссылки (документы ссылаются друг на друга). Чтобы программа не вошла в бесконечный цикл, обязательно используем коллекцию для кэширования уже обработанных ссылок (например, Соответствие).
Посмотрим на пример итеративного сбора подчиненных документов (поиск в ширину с защитой от зацикливания):
Функция ПолучитьВсеПодчиненныеДокументыВниз(ИсходныйДокумент) Экспорт
Результат = Новый Массив;
ОбработанныеСсылки = Новый Соответствие;
ОчередьОбработки = Новый Массив;
ОбработанныеСсылки.Вставить(ИсходныйДокумент, Истина);
ОчередьОбработки.Добавить(ИсходныйДокумент);
Пока ОчередьОбработки.Количество() > 0 Цикл
ТекущийДокумент = ОчередьОбработки[0];
ОчередьОбработки.Удалить(0);
НайденныеСсылки = ПолучитьПрямыеПодчиненныеДокументы(ТекущийДокумент);
Для Каждого ПодчиненнаяСсылка Из НайденныеСсылки Цикл
// Пропускаем сам исходный документ и уже посещенные документы
Если ОбработанныеСсылки[ПодчиненнаяСсылка] = Неопределено Тогда
ОбработанныеСсылки.Вставить(ПодчиненнаяСсылка, Истина);
ОчередьОбработки.Добавить(ПодчиненнаяСсылка);
Результат.Добавить(ПодчиненнаяСсылка);
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат Результат;
КонецФункции
УстановитьПривилегированныйРежим(Истина).СвязанныеДокументы, иначе динамический генератор запроса их не увидит.НайтиПоСсылкам(), однако он инициирует полный поиск по всей информационной базе и не рекомендуется для регулярного использования в транзакциях и экранных формах из-за высокой нагрузки на СУБД.