При разработке отчетов и обработок в среде 1С:Предприятие 7.7 часто возникает задача фильтрации данных по нескольким группам справочника. На форме обработки размещается элемент диалога типа СписокЗначений, в который пользователь может добавить одну или несколько папок номенклатуры (например, «Напитки», «Кеги», «Кондитерские изделия»). В процессе обработки данных требуется не просто проверить факт вхождения номенклатурной позиции в выбранный список, а сопоставить конкретный товар с той папкой из списка, в которой он фактически находится, для последующего вывода или заполнения таблицы значений.
Рассмотрим частые ошибки при решении этой задачи и разберем корректные способы обхода и сопоставления данных.
Разберем типичные заблуждения, с которыми сталкиваются разработчики:
Сообщить(СписокЗначений): если передать в процедуру Сообщить() переменную, содержащую объект типа СписокЗначений, платформа не будет перечислять элементы или строки списка, а выведет стандартное строковое представление агрегатного типа данных — "СписокЗначений".Группа.Принадлежит(...): у объекта справочника нет метода с таким именем. Метод Принадлежит() существует у объекта СписокЗначений. Если попытаться вызвать его у самого элемента справочника, платформа выдаст фатальную ошибку: Поле агрегатного объекта не обнаружено (Принадлежит).ПринадлежитГруппе().Проанализируем базовый способ решения. Если у нас есть проверяемый элемент номенклатуры (например, ТекТовар) и список значений ВыбТовар, содержащий выбранные папки, мы можем организовать цикл по строкам этого списка значений.
Посмотрим на пример правильной реализации:
Для к = 1 По ВыбТовар.РазмерСписка() Цикл
ЗначениеСписка = ВыбТовар.ПолучитьЗначение(к);
// Проверяем, что значение в списке действительно является группой справочника
Если ЗначениеСписка.ЭтоГруппа() = 1 Тогда
// Проверяем принадлежность текущего товара данной группе
Если ТекТовар.ПринадлежитГруппе(ЗначениеСписка) = 1 Тогда
Сообщить("Товар " + ТекТовар + " входит в группу " + ЗначениеСписка);
// Если товар не может входить в две разные ветки одновременно, цикл можно прервать
Прервать;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Обратим внимание на синтаксис: метод ПринадлежитГруппе(<Группа>) вызывается у проверяемого элемента, а целевая группа передается ему в качестве параметра.
Вызов метода ПринадлежитГруппе() внутри вложенных циклов создает нагрузку на базу данных при обработке больших массивов номенклатуры. В платформе 1С 7.7 метод СписокЗначений.Принадлежит(<Значение>) обладает встроенной поддержкой иерархии справочников: если в списке находятся группы, платформа автоматически определяет, принадлежит ли переданный элемент хотя бы одной из этих групп или подгрупп.
Однако метод Принадлежит() у списка значений возвращает только число 1 (истина) или 0 (ложь), не сообщая, какая конкретно группа совпала. Поэтому оптимально совмещать быструю проверку с детальным обходом:
// Сначала быстрая проверка: входит ли товар хоть в одну из групп списка вообще
Если ВыбТовар.Принадлежит(ТекТовар) = 1 Тогда
// Если входит, детализируем конкретную группу
Для к = 1 По ВыбТовар.РазмерСписка() Цикл
ЗначениеСписка = ВыбТовар.ПолучитьЗначение(к);
Если ТекТовар.ПринадлежитГруппе(ЗначениеСписка) = 1 Тогда
Сообщить("Найдена целевая группа: " + ЗначениеСписка);
Прервать;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Такой подход позволяет не выполнять перебор списка для товаров, которые заведомо не относятся ни к одной из выбранных пользователем групп.
Если на форме выбран список из десятков групп, перебирать весь список значений для каждого товара становится накладно. В иерархических справочниках 1С 7.7 глубина вложенности ограничена (не более 10 уровней). Гораздо быстрее подниматься от текущего товара вверх по цепочке Родитель и искать каждого родителя в списке значений через метод точного поиска НайтиЗначение(), который работает мгновенно в оперативной памяти.
Разберем реализацию этого алгоритма:
ТекРодитель = ТекТовар.Родитель;
ЦелеваяГруппа = ПолучитьПустоеЗначение("Справочник.ТМЦ");
Пока ТекРодитель.Выбран() = 1 Цикл
// Ищем прямое совпадение родителя в списке групп на форме
Если ВыбТовар.НайтиЗначение(ТекРодитель) > 0 Тогда
ЦелеваяГруппа = ТекРодитель;
Прервать; // Найдена ближайшая группа
КонецЕсли;
ТекРодитель = ТекРодитель.Родитель;
КонецЦикла;
Если ПустоеЗначение(ЦелеваяГруппа) = 0 Тогда
Сообщить("Товар " + ТекТовар + " принадлежит группе " + ЦелеваяГруппа);
КонецЕсли;
ПринадлежитГруппе() условие выполнится для обеих. При обходе по Способу 1 будет возвращена та группа, которая расположена в списке выше. При использовании Способа 3 будет определена наиболее глубокая (ближайшая к товару) группа.Группа.ПринадлежитГруппе(Группа) возвращает 1. Если в алгоритм обработки может попасть не только конечный элемент, но и сама группа, следует предварительно проверять признак ЭтоГруппа().