При работе в программе 1С:Управление торговлей 11 (УТ 11) менеджеры по продажам и закупкам постоянно сталкиваются с необходимостью видеть актуальные остатки товаров, свободные резервы, розничные или оптовые цены, а также штрихкоды непосредственно в форме списка справочника Номенклатура. Стандартный функционал не всегда выводит эти данные в удобном виде, особенно если требуется кастомизация под специфику бизнеса.
В этой статье мы подробно рассмотрим, как правильно решить данную задачу. Мы разберем как простые способы настройки динамического списка через произвольный запрос, так и профессиональные высокопроизводительные методы программирования, которые предотвратят зависание базы данных при работе десятков пользователей.
Часто на форумах советуют простое решение: изменить запрос динамического списка формы номенклатуры, добавив туда конструкцию ЛЕВОЕ СОЕДИНЕНИЕ с виртуальной таблицей остатков РегистрНакопления.ТоварыНаСкладах.Остатки и срезом цен РегистрСведений.ЦеныНоменклатуры.СрезПоследних. Рассмотрим, почему на реальной базе данных этот способ приводит к критическим проблемам:
ШтрихкодыНоменклатуры, вы получите дублирование позиций на экране. У одного товара может быть несколько штрихкодов (для штуки, коробки, паллеты), и левое соединение выведет одну и ту же номенклатуру столько раз, сколько штрихкодов для нее зарегистрировано.Выясним, как обойти эти ограничения и реализовать задачу красиво, быстро и правильно.
Если вы уже настроили простой запрос динамического списка и заметили, что позиции с нулевым остатком пропадают из списка, проанализируем настройки вашего запроса. Это происходит из-за того, что в условиях связи или в параметрах виртуальной таблицы не учитывается специфика соединения.
Чтобы в списке отображалась вся номенклатура, включая те позиции, которых нет на складе, убедитесь, что вы используете именно ЛЕВОЕ СОЕДИНЕНИЕ, где в качестве левой (основной) таблицы выступает справочник Номенклатура, а в качестве правой — регистр остатков. Если использовать внутреннее соединение (СОЕДИНЕНИЕ), товары без остатков будут отсекаться.
Кроме того, вместо пустых ячеек в колонке остатка можно принудительно выводить ноль. Для этого в поле выбора запроса используйте функцию ЕСТЬNULL. Посмотрим на пример:
ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК Остаток
Если после этого в списке всё равно отображается пустое поле вместо нуля, это связано с настройками условного оформления или формата поля в свойствах элемента формы. Установите в свойствах колонки формат отображения числа, указав вывод нулевого значения как "0". Также убедитесь, что в настройках формы динамического списка (кнопка "Настройка" -> вкладка "Отбор") не установлена галочка отбора по ненулевым остаткам.
Рассмотрим самое современное и архитектурно верное решение, которое используется профессиональными разработчиками в крупных базах данных. Вместо того чтобы перегружать основной SQL-запрос динамического списка тяжелыми соединениями, мы будем получать остатки, резервы, цены и штрихкоды "на лету" — только для тех строк, которые пользователь видит на экране в данный момент (обычно это не более 30-40 позиций при стандартном разрешении монитора).
Разберем этот метод по шагам.
Откроем форму списка справочника Номенклатура. Перейдем в свойства динамического списка Список и откроем его схему компоновки данных или произвольный запрос. Добавим в запрос пустые поля-заглушки, которые не будут нагружать СУБД, но создадут структуру для вывода наших данных:
ВЫБРАТЬ
СправочникНоменклатура.Ссылка КАК Ссылка,
СправочникНоменклатура.Код КАК Код,
СправочникНоменклатура.Наименование КАК Наименование,
// Поля-заглушки для динамического заполнения на сервере
0 КАК Остаток,
0 КАК СвободныйОстаток,
0 КАК Цена,
"" КАК Штрихкод
ИЗ
Справочник.Номенклатура КАК СправочникНоменклатура
Перетащите созданные поля на форму в виде колонок таблицы списка.
Для элемента формы Список (наша таблица динамического списка) перейдем в палитру свойств, найдем событие ПриПолученииДанныхНаСервере и создадим процедуру-обработчик. Проанализируем программный код, который выполнит сбор необходимых данных в один быстрый микрозапрос:
&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
// Шаг 2.1. Собираем массив только тех элементов номенклатуры, которые отображаются на экране
МассивНоменклатуры = Новый Массив;
Для Каждого СтрокаСписок Из Строки Цикл
Если ТипЗнч(СтрокаСписок.Ключ) = Тип("СправочникСсылка.Номенклатура") Тогда
МассивНоменклатуры.Добавить(СтрокаСписок.Ключ);
КонецЕсли;
КонецЦикла;
// Если на экране нет строк, выходим из процедуры
Если МассивНоменклатуры.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Шаг 2.2. Выполняем один быстрый запрос к остаткам, резервам, ценам и штрихкодам по фильтру массива
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
| ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК Остаток
|ПОМЕСТИТЬ ВТ_Остатки
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (&МассивНоменклатуры)) КАК ТоварыНаСкладахОстатки
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| РаспределениеЗапасов.Номенклатура КАК Номенклатура,
| ЕСТЬNULL(РаспределениеЗапасов.РезервОстаток, 0) КАК Резерв
|ПОМЕСТИТЬ ВТ_Резервы
|ИЗ
| РегистрНакопления.РаспределениеЗапасов КАК РаспределениеЗапасов
|ГДЕ
| РаспределениеЗапасов.Номенклатура В(&МассивНоменклатуры)
| И РаспределениеЗапасов.Состояние = ЗНАЧЕНИЕ(Перечисление.СостоянияРаспределенияЗапасов.Резерв)
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ЦеныНоменклатурыСрезПоследних.Номенклатура КАК Номенклатура,
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
|ПОМЕСТИТЬ ВТ_Цены
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, Номенклатура В (&МассивНоменклатуры) И ВидЦены = &ВидЦены) КАК ЦеныНоменклатурыСрезПоследних
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ШтрихкодыНоменклатуры.Владелец КАК Номенклатура,
| ШтрихкодыНоменклатуры.Штрихкод КАК Штрихкод
|ПОМЕСТИТЬ ВТ_Штрихкоды
|ИЗ
| РегистрСведений.ШтрихкодыНоменклатуры КАК ШтрихкодыНоменклатуры
|ГДЕ
| ШтрихкодыНоменклатуры.Владелец В(&МассивНоменклатуры)
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Остатки.Номенклатура КАК Номенклатура,
| ВТ_Остатки.Остаток КАК Остаток,
| ЕСТЬNULL(ВТ_Резервы.Резерв, 0) КАК Резерв,
| ВТ_Остатки.Остаток - ЕСТЬNULL(ВТ_Резервы.Резерв, 0) КАК СвободныйОстаток,
| ЕСТЬNULL(ВТ_Цены.Цена, 0) КАК Цена,
| ЕСТЬNULL(ВТ_Штрихкоды.Штрихкод, """") КАК Штрихкод
|ИЗ
| ВТ_Остатки КАК ВТ_Остатки
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Резервы КАК ВТ_Резервы
| ПО ВТ_Остатки.Номенклатура = ВТ_Резервы.Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Цены КАК ВТ_Цены
| ПО ВТ_Остатки.Номенклатура = ВТ_Цены.Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Штрихкоды КАК ВТ_Штрихкоды
| ПО ВТ_Остатки.Номенклатура = ВТ_Штрихкоды.Номенклатура";
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
// Задаем необходимый вид цены (например, розничную или оптовую цену)
// В реальной задаче вид цены можно выносить в настройки формы или получать из настроек пользователя
Запрос.УстановитьПараметр("ВидЦены", Справочники.ВидыЦен.НайтиПоНаименованию("Розничная"));
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
// Шаг 2.3. Заполняем полученными данными строки, отображаемые на экране пользователя
Для Каждого СтрокаСписок Из Строки Цикл
ТекущаяНоменклатура = СтрокаСписок.Ключ;
НайденныеСтроки = ТаблицаДанных.НайтиСтроки(Новый Структура("Номенклатура", ТекущаяНоменклатура));
Если НайденныеСтроки.Количество() > 0 Тогда
// Записываем данные в оформление строки (в наши колонки-заглушки)
СтрокаСписок.Значение.Данные["Остаток"] = НайденныеСтроки[0].Остаток;
СтрокаСписок.Значение.Данные["СвободныйОстаток"] = НайденныеСтроки[0].СвободныйОстаток;
СтрокаСписок.Значение.Данные["Цена"] = НайденныеСтроки[0].Цена;
// Решаем проблему дублирования штрихкодов: соединяем все штрихкоды товара в одну строку через запятую
МассивШтрихкодов = Новый Массив;
Для Каждого СтрокаШК Из НайденныеСтроки Цикл
Если ЗначениеЗаполнено(СтрокаШК.Штрихкод) И МассивШтрихкодов.Найти(СтрокаШК.Штрихкод) = Неопределено Тогда
МассивШтрихкодов.Добавить(СтрокаШК.Штрихкод);
КонецЕсли;
КонецЦикла;
СтрокаСписок.Значение.Данные["Штрихкод"] = СтрСоединить(МассивШтрихкодов, ", ");
Иначе
// Если данных нет, гарантируем вывод нулей и пустых строк
СтрокаСписок.Значение.Данные["Остаток"] = 0;
СтрокаСписок.Значение.Данные["СвободныйОстаток"] = 0;
СтрокаСписок.Значение.Данные["Цена"] = 0;
СтрокаСписок.Значение.Данные["Штрихкод"] = "";
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Рассмотрим, почему написанный алгоритм работает безупречно и быстро:
Номенклатура В (&МассивНоменклатуры), где содержится всего лишь около 30 ссылок. Индексы СУБД в этом случае работают с максимальной эффективностью.СтрСоединить мы сворачиваем дубли в одну аккуратную строку: "4601234567890, 4601234567891", не размножая при этом строки в самой таблице.РаспределениеЗапасов. Обратите внимание: в современных редакциях УТ 11 (начиная с 11.4 и выше) резервы хранятся именно в этом регистре, а не в старом СвободныеОстатки.Если перед вами стоит задача показать остатки товара не в общем количестве по предприятию, а детально в разрезе всех доступных складов компании (как просили в сообщении 16), то вывод множества колонок (по колонке на каждый склад) сделает форму списка перегруженной и нечитаемой. При добавлении нового склада разработчику придется постоянно переписывать код конфигурации.
В этом случае отличной альтернативой является создание вспомогательной информационной панели внизу или сбоку от основного списка номенклатуры (классический интерфейс подбора из УТ 10.3):
ОстаткиПоСкладам. Источником данных для нее сделаем отдельный легкий динамический список или просто таблицу значений.ПриАктивизацииСтроки будем получать ссылку на текущий выделенный товар.
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
ТекущаяСтрока = Элементы.Список.ТекущиеДанные;
Если ТекущаяСтрока <> Неопределено Тогда
ПодключитьОбработчикОжидания("ОбновитьОстаткиПоСкладам", 0.1, Истина);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ОбновитьОстаткиПоСкладамНаСервере(Знач ВыбранныйТовар)
// Устанавливаем параметр для подчиненного динамического списка остатков по складам
СписокОстатковПоСкладам.Параметры.УстановитьЗначениеПараметра("Номенклатура", ВыбранныйТовар);
КонецПроцедуры
В подчиненном списке остатков по складам запрос будет предельно простым. Он выберет только те склады, на которых данный товар физически присутствует в наличии:
ВЫБРАТЬ
ТоварыНаСкладахОстатки.Склад КАК Склад,
ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Остаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура = &Номенклатура) КАК ТоварыНаСкладахОстатки
Такой подход гарантирует, что основная форма номенклатуры открывается мгновенно, не тормозит при скроллинге, а менеджер при выборе конкретного товара сразу видит детальную и наглядную картину по всем складам компании в отдельном удобном окне.
Используя предложенные методы, вы сможете оптимизировать интерфейс 1С:Управление торговлей 11, сделав его максимально информативным для пользователей и безопасным для стабильности серверной инфраструктуры предприятия.