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