Как получить все уровни подчиненных заказов на производство в 1С

Программист 1С v8.3 (Управляемые формы) 1С:ERP Управление предприятием Управленческий учет Промышленность, строительство и АПК
← На главную

В практике разработки на платформе 1С часто возникает задача получения полной структуры подчиненности документов. Особую сложность это представляет для заказов на производство, где иерархия может достигать 5 и более уровней вложенности (например, когда один заказ порождает другие для производства полуфабрикатов). Попытка решить эту задачу одним запросом наталкивается на ограничение: язык запросов 1С не поддерживает рекурсивные запросы (CTE), которые доступны в «чистом» SQL.

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

Почему один запрос — это не всегда лучшее решение

Рассмотрим ситуацию: у нас есть «родительский» заказ, который имеет подчиненные заказы. Те, в свою очередь, могут иметь свои собственные подчиненные документы. Если мы попытаемся написать один запрос, нам придется заранее знать максимальную глубину вложенности. Для 5 уровней это будет выглядеть как 5 левых соединений таблицы КритерийОтбора.СвязанныеДокументы или таблиц документов самих с собой. Такое решение:

Поэтому мы разберем более гибкие программные методы.

Способ 1. Итерационный обход через критерий отбора в цикле

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

Разберем алгоритм по шагам:

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

Пример реализации такого кода:


// Массив для хранения всех найденных подчиненных заказов
ВсеНайденныеЗаказы = Новый Массив;
// Массив для поиска на текущем шаге итерации
ТекущиеРодители = Новый Массив;
ТекущиеРодители.Добавить(ИсходныйЗаказ);

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	СвязанныеДокументы.Ссылка КАК Ссылка
    |ИЗ
    |	КритерийОтбора.СвязанныеДокументы(&МассивРодителей) КАК СвязанныеДокументы
    |ГДЕ
    |	СвязанныеДокументы.Ссылка ССЫЛКА Документ.ЗаказНаПроизводство";

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

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

Способ 2. Использование механизмов Библиотеки Стандартных Подсистем (БСП)

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

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

Разберем пример вызова функции:


// Подготовим параметры для формирования дерева
Параметры = СтруктураПодчиненности.ПараметрыОтчета();
Параметры.Объекты = Новый Массив;
Параметры.Объекты.Добавить(ИсходныйЗаказ);

// Вызываем формирование дерева связей
// Метод возвращает дерево или таблицу со всеми уровнями
ДеревоСвязей = СтруктураПодчиненности.СформироватьДерево(Параметры);

// Теперь можно выбрать из дерева только заказы на производство
// Используем метод НайтиСтроки или обход дерева рекурсией
ПараметрыОтбора = Новый Структура;
// Фильтр по типам в ТЗ дерева обычно делается через колонку со ссылкой
Для Каждого СтрокаДерева Из ДеревоСвязей.Строки Цикл
    // Здесь ваша логика обработки строк
КонецЦикла;

Использование БСП предпочтительнее, так как разработчики платформы уже учли нюансы производительности и работы с различными типами связей (через реквизиты и через критерии отбора).

Способ 3. Получение сумм по подчиненным заказам

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

В сложных структурах один документ может быть связан с несколькими родительскими документами (например, через критерий отбора или специфические табличные части). Если мы просто будем суммировать данные каждого найденного заказа в цикле, мы рискуем получить двойной счет (double counting).

Правильный алгоритм сбора сумм:

  1. Сначала соберите все уникальные ссылки на ЗаказНаПроизводство одним из методов, описанных выше.
  2. Поместите этот список уникальных ссылок в массив.
  3. Выполните один итоговый запрос к табличной части или регистру этих заказов, передав массив ссылок в качестве параметра ГДЕ Ссылка В (&МассивЗаказов).

Пример итогового запроса для получения сумм:


ЗапросСумм = Новый Запрос;
ЗапросСумм.Текст = 
    "ВЫБРАТЬ
    |	СУММА(ЗаказНаПроизводство.СуммаДокумента) КАК ОбщаяСумма
    |ИЗ
    |	Документ.ЗаказНаПроизводство КАК ЗаказНаПроизводство
    |ГДЕ
    |	ЗаказНаПроизводство.Ссылка В (&МассивЗаказов)";

ЗапросСумм.УстановитьПараметр("МассивЗаказов", ВсеНайденныеЗаказы);
Результат = ЗапросСумм.Выполнить().Выбрать();
Если Результат.Следующий() Тогда
    Итог = Результат.ОбщаяСумма;
КонецЕсли;

Подведем итоги

Для получения всех подчиненных заказов на глубину до 5 уровней и более:

Следуя этим рекомендациям, вы сможете эффективно обрабатывать иерархические структуры любой сложности в 1С.

← На главную