Как программно получить подчиненные документы в 1С:Управление торговлей 11?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет IT и автоматизация бизнеса
← На главную

При разработке учетных механизмов и печатных форм часто возникает необходимость получить дерево или плоский список всех документов, которые были введены на основании конкретного документа (например, найти все счета-фактуры, приходные кассовые ордера, акты или корректировки, подчиненные документу РеализацияТоваровУслуг).

При попытке использовать встроенный метод КритерииОтбора.СвязанныеДокументы.Найти(Ссылка) разработчики нередко сталкиваются с ситуацией, когда метод возвращает только сам исходный документ либо только прямые связи первого уровня. Разберем детально, почему это происходит и как правильно организовать программный сбор всех подчиненных документов «вниз» по иерархии в конфигурациях «1С:Управление торговлей 11», «1С:Комплексная автоматизация» и «1С:ERP Управление предприятием».

Почему стандартный метод критерия отбора не находит всю цепочку?

Проанализируем механику работы критериев отбора в платформе 1С:Предприятие 8:

  1. Поиск только первого уровня: метод КритерииОтбора.СвязанныеДокументы.Найти() выполняет выборку только тех объектов, в реквизитах которых непосредственно записана переданная ссылка. Всю иерархию ниже первого уровня платформа автоматически не разворачивает.
  2. Зацикливание и попадание исходного документа: исходный документ попадает в результат отбора, если в его собственных реквизитах (например, ДокументОснование, ПервичныйДокумент или строках табличных частей) содержится ссылка на самого себя или циклические связи цепочки.
  3. Ограничения состава критерия: связь между документами в типовых конфигурациях может фиксироваться не только реквизитами шапки, но и движениями в регистрах или табличных частях. Если реквизит не включен в состав критерия отбора, стандартный метод его пропустит.

Где в типовых конфигурациях реализован механизм связанных документов?

В типовых конфигурациях на базе БСП («УТ 11», «КА 2», «ERP 2») дерево структуры подчиненности строится программно. Логика формирования запросов сосредоточена в модулях подсистемы структуры подчиненности:

  1. Обратите внимание на общую форму СвязанныеДокументы (ее модуль формы и вызовы серверных процедур).
  2. Общий модуль СтруктураПодчиненностиСлужебный и переопределяемый модуль СтруктураПодчиненностиПереопределяемый.

В этих модулях на основе метаданных Метаданные.КритерииОтбора.СвязанныеДокументы.Состав строится динамический запрос с условиями вида ДокументОснование = &ЗначениеКритерияОтбора или ПервичныйДокумент = &ЗначениеКритерияОтбора.

Способ 1. Получение прямых подчиненных документов через метаданные критерия отбора

Рассмотрим, как можно самостоятельно построить динамический запрос по всем объектам, входящим в состав критерия отбора СвязанныеДокументы. Этот подход позволяет получить все подчиненные документы первого уровня для переданной ссылки.


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

Способ 2. Рекурсивный или итеративный обход всего дерева документов «вниз»

Поскольку подчиненные документы могут, в свою очередь, порождать следующие документы (например: ЗаказКлиента → РеализацияТоваровУслуг → СчетФактураВыданный), для поиска всех веток вниз требуется обход цепочек.

Важно: в реальных базах данных возможны циклические ссылки (документы ссылаются друг на друга). Чтобы программа не вошла в бесконечный цикл, обязательно используем коллекцию для кэширования уже обработанных ссылок (например, Соответствие).

Посмотрим на пример итеративного сбора подчиненных документов (поиск в ширину с защитой от зацикливания):


Функция ПолучитьВсеПодчиненныеДокументыВниз(ИсходныйДокумент) Экспорт
    
    Результат = Новый Массив;
    ОбработанныеСсылки = Новый Соответствие;
    ОчередьОбработки = Новый Массив;
    
    ОбработанныеСсылки.Вставить(ИсходныйДокумент, Истина);
    ОчередьОбработки.Добавить(ИсходныйДокумент);
    
    Пока ОчередьОбработки.Количество() > 0 Цикл
        
        ТекущийДокумент = ОчередьОбработки[0];
        ОчередьОбработки.Удалить(0);
        
        НайденныеСсылки = ПолучитьПрямыеПодчиненныеДокументы(ТекущийДокумент);
        
        Для Каждого ПодчиненнаяСсылка Из НайденныеСсылки Цикл
            
            // Пропускаем сам исходный документ и уже посещенные документы
            Если ОбработанныеСсылки[ПодчиненнаяСсылка] = Неопределено Тогда
                ОбработанныеСсылки.Вставить(ПодчиненнаяСсылка, Истина);
                ОчередьОбработки.Добавить(ПодчиненнаяСсылка);
                Результат.Добавить(ПодчиненнаяСсылка);
            КонецЕсли;
            
        КонецЦикла;
        
    КонецЦикла;
    
    Возврат Результат;
    
КонецФункции

Особенности применения и практические рекомендации

  1. Влияние RLS (прав доступа на уровне записей): если в базе включен механизм RLS, обращение к таблицам критерия отбора от имени обычного пользователя может не вернуть документы, на которые у него нет прав чтения, что оборвет цепочку дальнейшего поиска. Если требуется получить полную технологическую цепочку документов без ограничений текущего сеанса, оборачивайте вызов в привилегированный модуль или используйте инструкцию УстановитьПривилегированныйРежим(Истина).
  2. Полнота критерия отбора: если в вашей конфигурации используются расширения или добавлены нестандартные реквизиты документов, убедитесь, что они включены в состав критерия отбора СвязанныеДокументы, иначе динамический генератор запроса их не увидит.
  3. Альтернативный метод: в редких сервисных задачах можно применять глобальный метод НайтиПоСсылкам(), однако он инициирует полный поиск по всей информационной базе и не рекомендуется для регулярного использования в транзакциях и экранных формах из-за высокой нагрузки на СУБД.
← На главную