Как правильно программно обойти дерево себестоимости в 1С:ERP и почему подчиненные строки оказываются пустыми?

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

При разработке отчетов и обработок в среде 1С:ERP Управление предприятием 2 программисты часто сталкиваются с необходимостью выгрузить или проанализировать данные отчета «Дерево себестоимости». Однако стандартная попытка рекурсивного обхода строк дерева на клиенте или на сервере нередко приводит к неожиданному результату: система успешно обходит только строки верхнего уровня, а при попытке провалиться глубже выдает лишь одну пустую строку-пустышку для каждого подчиненного узла, хотя на экране пользователь видит полноценное заполненное дерево со множеством ветвей. Для получения структуры затрат без собственной разработки можно использовать отчет для анализа себестоимости продукции по заказу клиента.

В этой статье мы подробно разберем, почему возникает этот эффект, исследуем внутренние механизмы платформы «1С:Предприятие 8.3» и рассмотрим правильные архитектурные подходы для получения полной структуры дерева себестоимости без привязки к состоянию элементов интерфейса.


Выясняем причину: механизм «ленивой загрузки» (Lazy Loading)

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

Давайте проанализируем, как система работает под капотом:

  1. Передача тяжелого многоуровневого дерева себестоимости с сервера на тонкий клиент по протоколу HTTP — крайне ресурсоемкая операция. Если дерево содержит тысячи номенклатурных позиций и полуфабрикатов, мгновенная передача всей структуры на клиент заставит интерфейс «зависнуть».
  2. Для решения этой проблемы разработчики платформы и конфигурации ERP используют оптимизацию: при первоначальном выводе дерева на форму с сервера передается только самый верхний уровень данных.
  3. Для узлов, у которых гарантированно есть подчиненные элементы, система автоматически создает одну пустую маркерную строку. Наличие этой пустышки сообщает интерфейсу клиента, что у строки есть дочерние элементы, и платформа рисует символ «плюсик» (+) рядом с узлом.
  4. В момент, когда пользователь нажимает на «плюсик» в интерфейсе, на сервере срабатывает обработчик события ПередРазворачиванием. Только в этот самый момент система выполняет контекстный вызов, удаляет маркерную строку и подгружает из базы реальные подчиненные данные для этого конкретного узла.

Именно поэтому, когда мы пытаемся программно прочитать дерево через вызов РеквизитФормыВЗначение("ПолноеДеревоСебестоимости") или обойти его на клиенте с помощью метода ПолучитьЭлементы(), мы натыкаемся на пустые строки. Если пользователь визуально не развернул все ветки дерева на форме, этих данных физически нет в оперативной памяти и в реквизитах формы на клиенте!


Анализ неработающего кода

Давайте посмотрим на пример кода, который часто пишут разработчики и который гарантированно приводит к ошибке считывания «свернутых» данных.

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


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

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

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


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

&НаКлиенте
Процедура ПоказатьТаблицу(Команда)
    ОбойтиДеревоЗначений(Items.ПолноеДеревоСебестоимости); // Работа напрямую с элементами формы
КонецПроцедуры

Оба эти варианта не дадут нужного результата, так как они работают непосредственно со слоем представления (интерфейсом), где данные еще не инициализированы.


Решение проблемы: Способ 1. Получение данных из Временного хранилища (Рекомендуемый)

Самый правильный и архитектурно верный способ работы с данными в управляемом приложении — это полное разделение интерфейса и бизнес-логики. Не нужно пытаться читать данные с элементов формы. Гораздо надежнее забрать их напрямую из источника, в котором они были сформированы.

В типовой архитектуре 1С:ERP при построении отчета «Дерево себестоимости» дерево рассчитывается на сервере и помещается в специальный объект во Временном хранилище. Адрес этого хранилища обычно передается в параметрах формы или доступен в качестве свойства формы отчета.

Рассмотрим по шагам, как прочитать полные данные напрямую из временного хранилища:

  1. Определяем адрес временного хранилища, куда отчет помещает результат расчета.
  2. Получаем объект типа ДеревоЗначений на сервере.
  3. Выполняем полный рекурсивный обход данных, который теперь гарантированно вернет все уровни вложенности.

Пример программной реализации этого подхода:


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

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

Решение проблемы: Способ 2. Программное разворачивание дерева на форме

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

Сделать это на клиенте можно с помощью метода таблицы формы Развернуть(). Давайте разберем этот процесс на примере:


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

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

Важное примечание: Использование Способа 2 крайне нежелательно на больших базах данных. Программное раскрытие сотен веток на клиенте приведет к лавинообразному количеству контекстных вызовов сервера, что может сильно замедлить работу системы. Отдавайте предпочтение работе с временным хранилищем на сервере (Способ 1).


Важные ограничения 1С:ERP по расчету себестоимости

Разрабатывая собственные печатные формы или выгрузки дерева себестоимости в Excel, обязательно учитывайте ключевую особенность архитектуры ERP:

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

← На главную