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