Как реализовать подбор товаров с характеристиками, остатками и ценами с сохранением иерархии

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

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

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

В чем заключается основная сложность?

Проблема иерархического подбора с характеристиками кроется в самой структуре данных. Справочник Номенклатура имеет иерархию (папки и элементы), а данные об остатках и ценах в регистрах хранятся в «плоском» виде: Номенклатура — Характеристика — Количество. Если мы просто выберем данные из регистра остатков, мы потеряем структуру папок. Если мы выберем данные из справочника номенклатуры, мы не увидим характеристик (так как они обычно хранятся в подчиненном справочнике или определяются набором свойств).

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

Шаг 1. Проектирование запроса для получения данных

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

  1. Полное соединение регистров Остатки и Цены. Это нужно для того, чтобы увидеть товар, если у него есть цена, но нет остатка, или наоборот.
  2. Левое соединение справочника Номенклатура с результатом первого шага. Это позволит нам вывести все позиции номенклатуры из справочника, «подтянув» к ним данные из регистров там, где они есть.

Проанализируем пример запроса, который решает эту задачу:


ВЫБРАТЬ РАЗРЕШЕННЫЕ
    ЕСТЬNULL(ОстаткиНаСкладахОстатки.Характеристика, ЦеныНоменклатурыСрезПоследних.Характеристика) КАК Характеристика,
    ЕСТЬNULL(ЦеныНоменклатурыСрезПоследних.Цена, 0) КАК Цена,
    ЕСТЬNULL(ОстаткиНаСкладахОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток,
    ОстаткиНаСкладахОстатки.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_ДанныеРегистров
ИЗ
    РегистрНакопления.ОстаткиНаСкладах.Остатки(&Дата, Склад = &Склад) КАК ОстаткиНаСкладахОстатки
    ПОЛНОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, ВидЦены = &ВидЦены) КАК ЦеныНоменклатурыСрезПоследних
    ПО ОстаткиНаСкладахОстатки.Номенклатура = ЦеныНоменклатурыСрезПоследних.Номенклатура
    И ОстаткиНаСкладахОстатки.Характеристика = ЦеныНоменклатурыСрезПоследних.Характеристика;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    СпрНоменклатура.Ссылка КАК Номенклатура,
    СпрНоменклатура.ЭтоГруппа КАК ЭтоГруппа,
    ВТ.Характеристика КАК Характеристика,
    ВТ.Цена КАК Цена,
    ВТ.КоличествоОстаток КАК КоличествоОстаток
ИЗ
    Справочник.Номенклатура КАК СпрНоменклатура
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДанныеРегистров КАК ВТ
    ПО СпрНоменклатура.Ссылка = ВТ.Номенклатура
ГДЕ
    СпрНоменклатура.Родитель = &Родитель

Обратите внимание на использование функции ЕСТЬNULL. Она критически важна: если данных в одном из регистров нет, соединение вернет NULL, что приведет к некорректному отображению или ошибкам в расчетах. Мы заменяем NULL на пустую ссылку характеристики или нулевую цену/остаток.

Шаг 2. Реализация интерфейса (Дерево или Таблица?)

Рассмотрим ситуацию, когда стандартный динамический список нам не подходит (например, нужно программно манипулировать строками или иерархия в списке работает не так, как ожидалось). Мы можем использовать Таблицу значений или Дерево значений на форме.

Для реализации иерархии «как в типовых» (слева — дерево папок, справа — список товаров) удобно разделить форму на две части:

  1. Реквизит «ИерархияНоменклатуры»: Динамический список только по папкам справочника Номенклатура.
  2. Реквизит «СписокТоваров»: Таблица значений, которая пересчитывается при выборе папки в левой части.

Разберем программный код серверной процедуры, которая заполняет список товаров при смене родителя:


&НаСервере
Процедура ЗаполнитьСписокНаСервере(Родитель)
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ ... (текст запроса из Шага 1) ...";
    
    Запрос.УстановитьПараметр("ВидЦены", Объект.ВидЦены);
    Запрос.УстановитьПараметр("Дата", ТекущаяДата());
    Запрос.УстановитьПараметр("Склад", Объект.Склад);
    Запрос.УстановитьПараметр("Родитель", Родитель);
    
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    // Очищаем таблицу на форме перед заполнением
    СписокТоваров.Очистить();
    
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = СписокТоваров.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
    КонецЦикла;
    
КонецПроцедуры

Шаг 3. Обработка выбора и корзина товаров

Когда пользователь выбирает товар в списке, мы должны добавить его в «корзину» (таблицу отобранных товаров). Важно помнить, что идентификатором уникальности теперь выступает не только Номенклатура, но и пара Номенклатура + Характеристика.

Рассмотрим пример обработки события выбора, где мы проверяем, не был ли этот товар добавлен ранее:


&НаКлиенте
Процедура СписокТоваровВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    ТекДанные = Элементы.СписокТоваров.ТекущиеДанные;
    
    Если ТекДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;

    // Параметры для поиска существующей строки в корзине
    ПараметрыПоиска = Новый Структура;
    ПараметрыПоиска.Вставить("Номенклатура", ТекДанные.Номенклатура);
    ПараметрыПоиска.Вставить("Характеристика", ТекДанные.Характеристика);
    
    НайденныеСтроки = ОтобранныеТовары.НайтиСтроки(ПараметрыПоиска);
    
    Если НайденныеСтроки.Количество() > 0 Тогда
        // Товар уже есть в корзине, увеличиваем количество
        СтрокаКорзины = НайденныеСтроки[0];
        СтрокаКорзины.Количество = СтрокаКорзины.Количество + 1;
        СтрокаКорзины.Сумма = СтрокаКорзины.Количество * СтрокаКорзины.Цена;
    Иначе
        // Добавляем новую позицию
        НоваяСтрока = ОтобранныеТовары.Добавить();
        НоваяСтрока.Номенклатура = ТекДанные.Номенклатура;
        НоваяСтрока.Характеристика = ТекДанные.Характеристика;
        НоваяСтрока.Количество = 1;
        НоваяСтрока.Цена = ТекДанные.Цена;
        НоваяСтрока.Сумма = ТекДанные.Цена;
    КонецЕсли;
    
КонецПроцедуры

Дополнительные рекомендации по оптимизации

Чтобы подбор работал быстро даже на больших базах, проанализируем следующие моменты:

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

← На главную