Как хранить и отображать группы справочников в табличной части документа 1С

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

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

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

Физическое хранение групп справочников в СУБД

С технической точки зрения (на уровне таблиц СУБД) ссылка на группу справочника и ссылка на элемент имеют абсолютно одинаковый тип данных — СправочникСсылка.<ИмяСправочника>. Обе они представляют собой уникальный 128-битный идентификатор (UUID). Платформа «1С:Предприятие» без каких-либо ограничений позволяет записывать ссылки на группы в любые реквизиты табличных частей. База данных успешно сохранит эти данные.

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

Свойство реквизита «Выбор групп и элементов»

Проанализируем свойство реквизита метаданных ВыборГруппИЭлементов (в англоязычной версии — ChoiceFoldersAndItems). Этот параметр определяет поведение платформы при интерактивной работе пользователя:

  1. Элементы: Пользователь сможет выбрать в поле ТЧ только конечные элементы справочника. Группы будут отображаться в форме выбора, но кнопка выбора для них будет недоступна.
  2. Группы: Разрешен выбор только папок справочника.
  3. Группы и элементы: Универсальный режим, позволяющий выбирать любые объекты справочника.

Важный нюанс: Ограничение свойства ВыборГруппИЭлементов действует только на интерактивные действия пользователя (ввод на сервере/клиенте через форму выбора, автоподбор). Если мы выполняем заполнение табличной части программно, платформа полностью игнорирует это свойство!

Рассмотрим пример кода:


НоваяСтрока = Объект.Изделия.Добавить();
НоваяСтрока.Номенклатура = ТекущийЭлементНоменклатуры; 
// Программная запись группы в реквизит ТЧ
НоваяСтрока.Группа = ТекущийЭлементНоменклатуры.Родитель;

Этот код отработает без ошибок даже в том случае, если для реквизита Группа в конфигураторе жестко задано значение «Только элементы». Ошибок записи в базу данных не возникнет, пока на форму или в модуль объекта не будут добавлены кастомные проверки заполнения.

Почему пропадают данные при записи? Разбор частой ошибки

Рассмотрим ситуацию, с которой часто сталкиваются разработчики: код заполнения написан, в процессе выполнения строки ТЧ визуально заполняются, но после вызова метода Записать() табличная часть кажется абсолютно пустой.

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


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

Если перед вызовом этого алгоритма табличная часть документа уже содержала пустые или старые строки (например, созданные интерактивно при открытии формы), новые строки с заполненными группами будут добавлены в самый конец табличной части. При открытии формы пользователь видит первые строки таблицы, которые остались пустыми, и делает ложный вывод, что данные «исчезли» при записи. Достаточно прокрутить таблицу до конца, чтобы обнаружить все успешно записанные данные.

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

Проблема производительности (N+1 запросов) при получении родителей

Обратите внимание на строку НоваяСтрока.Группа = НоваяСтрока.Номенклатура.Родитель; в цикле. Обращение к реквизитам через точку от ссылочного типа (в данном случае получение родителя номенклатуры) приводит к неявному обращению к базе данных на каждой итерации цикла. Если в документе рассчитываются тысячи строк номенклатуры, система сделает тысячи отдельных запросов к СУБД, что вызовет жесткое падение производительности.

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


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

Как вывести табличную часть документа в виде иерархического дерева

Хранить группы в плоской таблице — это лишь половина дела. Для удобства пользователей интерфейс расчета себестоимости или калькуляции должен быть представлен в виде визуального дерева. Физически табличная часть документа в 1С всегда плоская. Напрямую отобразить её иерархически стандартными средствами платформы нельзя. Рассмотрим два основных способа решения этой задачи.

Способ 1: Реализация произвольной иерархии с помощью ключей связи

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

Проанализируем структуру реквизитов ТЧ:

На форму документа вместо стандартной таблицы выводится элемент управления с типом ДеревоЗначений (например, реквизит формы с именем ДеревоДанныхФормы). При открытии формы плоская ТЧ считывается и программно преобразуется в дерево. Перед записью документа выполняется обратная операция.

Рассмотрим программный код формирования дерева на форме при чтении документа:


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

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

Перед непосредственной записью документа на сервере нам необходимо выполнить обратную операцию — «выпрямить» иерархическое дерево обратно в плоскую структуру ТЧ документа. Для этого мы рекурсивно обходим все строки дерева значений и выгружаем их в табличную часть.


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

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

Способ 2: Виртуальное группирование по справочнику «Номенклатура»

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

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

Заключение

Мы выяснили, что платформа «1С:Предприятие» полностью поддерживает хранение групп справочников в табличных частях. При разработке таких алгоритмов всегда помните о своевременной очистке коллекций перед программным заполнением и избегайте обращений «через точку» в циклах к родительским реквизитам. Использование деревьев значений на управляемых формах позволит вам выводить плоские структуры документов в максимально удобном для пользователей виде.

← На главную