Как в 1С получить полную цепочку связанных документов?

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

В процессе работы с 1С часто возникает задача проследить всю историю хозяйственной операции: от первоначального заказа клиента до отгрузки и последующей оплаты. Все эти действия отражаются в системе разными документами, которые связаны между собой. Например, на основании документа "Заказ клиента" создается "Реализация товаров и услуг", а на основании реализации — "Счет-фактура выданный". В этой статье мы подробно разберем, как программно получить всю эту цепочку, используя стандартные механизмы платформы 1С. Также для анализа взаимосвязей может быть полезен универсальный поиск ссылок на справочники и документы в любых конфигурациях.

Проанализируем, в чем разница между отчетом "Структура подчиненности", который видят пользователи, и платформенными объектами, доступными разработчику. Рассмотрим основной инструмент для этой задачи — КритерийОтбора — и научимся с его помощью строить полное дерево связанных документов — для этой задачи есть обработка для построения дерева связей документов.

Пользовательский отчет и платформенный механизм: в чем разница?

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

Таким образом, наша задача — не найти одну волшебную функцию, а научиться использовать платформенные механизмы для самостоятельного построения нужной нам иерархии документов.

Использование КритерияОтбора.СвязанныеДокументы для поиска прямых потомков

Самый правильный и эффективный способ найти документы, введенные на основании другого документа, — это использовать объект КритерийОтбора. Это специальный, заранее сконфигурированный и проиндексированный механизм поиска, который работает значительно быстрее, чем ручной перебор или прямой запрос по неиндексированным полям. В случае, если в базе данных возникли проблемы с целостностью, может потребоваться поиск объекта по ГУИД для идентификации потерянных связей.

В платформе 1С существует стандартный критерий отбора — СвязанныеДокументы. Он настроен на поиск по реквизиту ДокументОснование (или аналогичному), который как раз и хранит ссылку на родительский документ. Важно помнить, что этот метод находит только прямых потомков, то есть документы, находящиеся на один уровень ниже в иерархии.

Рассмотрим по шагам, как его использовать.

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

Посмотрим на практический пример кода, который находит все подчиненные документы для некоего ДокументОснование.


// Предполагается, что переменная ДокументОснование уже содержит
// ссылку на исходный документ (например, Документы.ЗаказКлиента.НайтиПоНомеру(...))

// 1. Создаем структуру для отбора
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("СвязанныеДокументы", ДокументОснование);

// 2. Выполняем поиск по критерию отбора
// Метод Найти() вернет массив ссылок на все найденные документы
МассивПодчиненных = КритерииОтбора.СвязанныеДокументы.Найти(ПараметрыОтбора);

// 3. Обрабатываем результат
Сообщить("Найдены подчиненные документы для " + ДокументОснование + ":");
Для Каждого ПодчиненныйДокумент Из МассивПодчиненных Цикл
    Сообщить(" - " + ПодчиненныйДокумент);
КонецЦикла;

Этот код отлично решает задачу поиска документов на следующем уровне. Но что делать, если нам нужна вся цепочка? Например, найти счет-фактуру для заказа клиента, если между ними есть документ реализации? Для этого нам нужно обойти иерархию вглубь. Если при обходе вы обнаружите пустые ссылки, вам поможет обработка для поиска и замены битых ссылок.

Построение полной иерархии: рекурсивный подход

Чтобы собрать все дерево подчиненности, а не только первый уровень, нам нужно для каждого найденного документа повторить процедуру поиска его подчиненных. Эта задача идеально решается с помощью рекурсии — вызова функцией или процедурой самой себя.

Давайте создадим процедуру, которая будет принимать на вход документ и узел в дереве значений, находить его "детей" и для каждого из них вызывать саму себя. Это позволит нам обойти все ветви иерархии до самого конца. Важно следить за корректностью данных, чтобы не пропустить записи в регистрах, в этом поможет поиск и восстановление битых ссылок в регистрах.

Разберем по шагам создание такой процедуры.

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

Рассмотрим полный пример кода с комментариями.


&НаСервере
Процедура ПостроитьДеревоПодчиненности()
    
    // Начальный документ, с которого строим цепочку
    // В реальной задаче его нужно получить (например, из реквизита формы)
    СтартовыйДокумент = Документы.ЗаказКлиента.Выбрать().Следующий().Ссылка;
    Если НЕ ЗначениеЗаполнено(СтартовыйДокумент) Тогда
        Сообщить("Демонстрационный стартовый документ не найден!");
        Возврат;
    КонецЕсли;

    // 1. Готовим дерево для результата
    ДеревоДокументов = Новый ДеревоЗначений;
    ДеревоДокументов.Колонки.Добавить("Документ");

    // Добавляем корневой узел в дерево (наш стартовый документ)
    КорневаяСтрока = ДеревоДокументов.Строки.Добавить();
    КорневаяСтрока.Документ = СтартовыйДокумент;

    // Запускаем рекурсивное заполнение дерева, начиная с корневого элемента
    ЗаполнитьДеревоПодчиненныхДокументов(СтартовыйДокумент, КорневаяСтрока);
    
    // Теперь в переменной ДеревоДокументов хранится полная иерархия,
    // которую можно, например, вывести на форму.
    
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьДеревоПодчиненныхДокументов(ТекущийДокумент, РодительскаяСтрокаДерева)

    // Настраиваем отбор по документу-основанию
    ОтборПоОснованию = Новый Структура;
    ОтборПоОснованию.Вставить("СвязанныеДокументы", ТекущийДокумент.Ссылка);

    // Находим все прямые подчиненные документы
    НайденныеДокументы = КритерииОтбора.СвязанныеДокументы.Найти(ОтборПоОснованию);

    // Обходим найденные документы
    Для Каждого НайденныйДокумент Из НайденныеДокументы Цикл
        // Добавляем найденный документ как дочернюю строку в дерево
        НоваяСтрока = РодительскаяСтрокаДерева.Строки.Добавить();
        НоваяСтрока.Документ = НайденныйДокумент;

        // 4. Рекурсивный вызов для каждого найденного документа,
        // чтобы найти уже ЕГО подчиненные документы
        ЗаполнитьДеревоПодчиненныхДокументов(НайденныйДокумент, НоваяСтрока);
    КонецЦикла;

КонецПроцедуры

Этот подход является универсальным и самым правильным с точки зрения архитектуры платформы. Он будет работать в любой конфигурации и корректно обработает любые связи, построенные на стандартном механизме подчиненности.

Альтернатива: прямой запрос к данным

В редких случаях, когда иерархия документов очень глубокая и разветвленная, а производительность является критически важной, можно рассмотреть вариант с прямым запросом к базе данных. Для реализации сложной логики вывода данных может также пригодиться универсальная функция ПолучитьРезультатСКД(). Однако этот подход имеет свои недостатки.

Преимущества запроса:

Недостатки запроса:

В большинстве случаев использование КритерияОтбора в рекурсивной процедуре является предпочтительным, так как это более надежный, гибкий и легко поддерживаемый метод.

← На главную