При разработке механизмов ценообразования в 1С часто возникает задача: задать процент наценки на верхнем уровне каталога товаров, чтобы он автоматически применялся ко всей номенклатуре внутри этой группы, включая любые вложенные подгруппы произвольной глубины. Выясним причину, почему стандартное обращение через реквизит Родитель не дает нужного результата, и разберем по шагам правильные способы реализации этой логики.
Проанализируем типичные ошибки при попытке получить данные вышестоящих групп:
Ссылка.Родитель.ПроцентНаценки обращается только к непосредственному родителю текущего элемента. Если товар находится в подгруппе второго или третьего уровня, а процент наценки задан в корневой группе, у непосредственного родителя значение наценки будет равно нулю или не заполнено. Попытка прописать Ссылка.Родитель.Родитель лишь жестко фиксирует проверку на определенную глубину, ломая логику для элементов с другой структурой вложенности.В ИЕРАРХИИ: В языке запросов 1С оператор В ИЕРАРХИИ работает по направлению «сверху вниз» (проверяет, входит ли объект в указанный узел-родитель). При использовании условия соединения вида ПО НаценкиГруппы.Номенклатура В ИЕРАРХИИ (&НоменклатураГруппа) платформа ищет подчиненные записи относительно параметра, а не родителей текущей позиции.ТС.Номенклатура.Родитель.ПроцентНаценки) внутри цикла обхода строк табличной части генерирует неявные одиночные запросы к СУБД для каждой строки, что сильно замедляет проведение документов.Если требуется получить действующую наценку для конкретной номенклатуры при интерактивном открытии карточки или расчете единичной позиции, соберем всех родителей элемента в массив с помощью цикла по свойству Родитель.
Посмотрим на пример кода:
МассивРодителей = Новый Массив;
ТекРодитель = НоменклатураСсылка.Родитель;
Пока ЗначениеЗаполнено(ТекРодитель) Цикл
МассивРодителей.Добавить(ТекРодитель);
ТекРодитель = ТекРодитель.Родитель;
КонецЦикла;
Если МассивРодителей.Количество() > 0 Тогда
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Рег.Номенклатура КАК Группа,
| Рег.ПроцентНаценки КАК ПроцентНаценки
|ИЗ
| РегистрСведений.НаценкиПоПодразделениям КАК Рег
|ГДЕ
| Рег.Номенклатура В (&МассивРодителей)
| И Рег.Подразделение = &Подразделение";
Запрос.УстановитьПараметр("МассивРодителей", МассивРодителей);
Запрос.УстановитьПараметр("Подразделение", ПодразделениеСсылка);
ТЗНаценки = Запрос.Выполнить().Выгрузить();
// Ищем наценку от ближайшего родителя к корню
Для Каждого Группа Из МассивРодителей Цикл
НайденнаяСтрока = ТЗНаценки.Найти(Группа, "Группа");
Если НайденнаяСтрока <> Неопределено И НайденнаяСтрока.ПроцентНаценки <> 0 Тогда
НаценкаГруппы = НайденнаяСтрока.ПроцентНаценки;
Прервать;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Рассмотрим оптимальный подход для групповой обработки строк (например, при проведении документов «Изменение цен» или расчете цен по накладной). За один запрос выбираются все товары и соединяются со своими родителями на несколько уровней вверх. Действующая наценка определяется с помощью функции ЕСТЬNULL с учетом приоритета: товар → родитель 1-го уровня → родитель 2-го уровня → родитель 3-го уровня.
Разберем пример пакетного запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Ссылка КАК Номенклатура,
| Товары.Родитель КАК Родитель1,
| Товары.Родитель.Родитель КАК Родитель2,
| Товары.Родитель.Родитель.Родитель КАК Родитель3
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| Справочник.Номенклатура КАК Товары
|ГДЕ
| Товары.Ссылка В (&СписокТоваров)
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Номенклатура КАК Номенклатура,
| ЕСТЬNULL(НацТовара.ПроцентНаценки,
| ЕСТЬNULL(НацРод1.ПроцентНаценки,
| ЕСТЬNULL(НацРод2.ПроцентНаценки,
| ЕСТЬNULL(НацРод3.ПроцентНаценки, 0)))) КАК ИтоговаяНаценка
|ИЗ
| ВТ_Товары КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НаценкиПоПодразделениям КАК НацТовара
| ПО НацТовара.Номенклатура = ВТ.Номенклатура
| И НацТовара.Подразделение = &Подразделение
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НаценкиПоПодразделениям КАК НацРод1
| ПО НацРод1.Номенклатура = ВТ.Родитель1
| И НацРод1.Подразделение = &Подразделение
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НаценкиПоПодразделениям КАК НацРод2
| ПО НацРод2.Номенклатура = ВТ.Родитель2
| И НацРод2.Подразделение = &Подразделение
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НаценкиПоПодразделениям КАК НацРод3
| ПО НацРод3.Номенклатура = ВТ.Родитель3
| И НацРод3.Подразделение = &Подразделение";
Запрос.УстановитьПараметр("СписокТоваров", ТаблицаТоваров.ВыгрузитьКолонку("Номенклатура"));
Запрос.УстановитьПараметр("Подразделение", Подразделение);
РезультатНаценок = Запрос.Выполнить().Выгрузить();
Для объектной модели данных в 1С предусмотрен встроенный метод ссылки ПринадлежитЭлементу(). Он проверяет подчиненность элемента группе на любом уровне вложенности без необходимости вручную раскручивать дерево родителей.
Посмотрим на пример его использования в коде:
Если НоменклатураСсылка.ПринадлежитЭлементу(ГруппаСНаценкой) Тогда
// Товар входит в группу или в любую из ее подгрупп
ПроцентНаценки = ПолучитьНаценкуГруппы(ГруппаСНаценкой);
КонецЕсли;
Важно: Метод ПринадлежитЭлементу() выполняет последовательное считывание всех вышестоящих элементов из базы данных на момент вызова. Применяйте его точечно, избегая массового использования внутри больших циклов.
Привязка правил ценообразования к физической иерархии папок справочника часто приводит к усложнению запросов и ошибкам при перемещении товаров между группами. В типовых решениях 1С для этих целей используется реквизит Ценовая группа (или сегменты номенклатуры):
Номенклатура создается реквизит ЦеноваяГруппа (ссылка на отдельный справочник).