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