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