Как вывести остатки каждого склада в отдельных колонках динамического списка?

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

При разработке интерфейсов на платформе 1С:Предприятие 8.3 часто возникает задача отображения остатков товаров в разрезе складов непосредственно в списке номенклатуры. Стандартный функционал динамического списка ориентирован на линейный вывод данных, и он не умеет автоматически превращать строки (склады) в колонки (кросс-таблица), как это делает компоновка данных в отчетах. Однако, используя программные методы, мы можем добиться желаемого результата.

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

Суть проблемы и выбор стратегии

Динамический список работает на основе запроса. Если мы заранее не знаем количество складов, мы не можем жестко прописать колонки в конфигураторе. Нам необходимо решить две задачи:

  1. Программно сформировать текст запроса, где для каждого склада будет отдельное поле ВЫБОР КОГДА....
  2. Динамически создать элементы формы (колонки в таблице), так как добавление полей в запрос не выводит их на экран автоматически.

Рассмотрим по шагам, как это реализовать.

Способ 1. Программное изменение текста запроса и создание колонок

Этот метод подходит, когда складов немного (до 10-15), и пользователю важно видеть общую картину. Реализацию лучше всего выполнять в процедуре ПриСозданииНаСервере или при изменении параметров отбора.

Проанализируем алгоритм формирования текста запроса. Нам нужно перебрать все актуальные склады и для каждого добавить конструкцию ВЫБОР КОГДА. Рассмотрим пример кода:


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

КонецПроцедуры

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

Способ 2. Использование события «ПриПолученииДанныхНаСервере»

Если в вашей базе сотни складов или номенклатуры очень много, первый способ может замедлить работу динамического списка из-за огромного количества соединений в запросе. Начиная с версии платформы 8.3.10, рекомендуется использовать обработчик ПриПолученииДанныхНаСервере.

Выясним причину эффективности этого метода: расчет данных происходит не для всей таблицы, а только для тех строк, которые пользователь видит на экране в данный момент (обычно 30-40 строк). Мы создаем на форме пустые колонки, а значения в них подставляем "на лету".

Проанализируем шаги реализации:

  1. Создаем программно колонки (как в первом способе), но не меняем основной запрос списка.
  2. В процедуре ПриПолученииДанныхНаСервере получаем массив ссылок номенклатуры из коллекции Строки.
  3. Одним запросом получаем остатки по этим ссылкам в разрезе складов.
  4. Заполняем структуру Данные для каждой строки.

&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
    
    // Получаем массив ссылок номенклатуры, которые отрисовываются на экране
    МассивНоменклатуры = Строки.ПолучитьКлючи();
    
    // Выполняем запрос к остаткам только по этим позициям
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ Склад, Номенклатура, ВНаличииОстаток ИЗ РегистрНакопления.Запасы.Остатки ГДЕ Номенклатура В (&Массив)";
    Запрос.УстановитьПараметр("Массив", МассивНоменклатуры);
    
    Результат = Запрос.Выполнить().Выгрузить();
    
    // Обходим видимые строки и заполняем программные колонки
    Для Каждого СтрокаСписка Из Строки Цикл
        КлючНоменклатура = СтрокаСписка.Ключ;
        
        // Ищем остатки по складам для данной номенклатуры
        Отбор = Новый Структура("Номенклатура", КлючНоменклатура);
        СтрокиОстатков = Результат.НайтиСтроки(Отбор);
        
        Для Каждого СтрокаОстатка Из СтрокиОстатков Цикл
            ИмяКолонки = "Склад_" + СтрЗаменить(Строка(СтрокаОстатка.Склад.УникальныйИдентификатор()), "-", "");
            Если СтрокаСписка.Значение.Данные.Свойство(ИмяКолонки) Тогда
                СтрокаСписка.Значение.Данные[ИмяКолонки] = СтрокаОстатка.ВНаличииОстаток;
            КонецЕсли;
        КонецЦикла;
    КонецЦикла;
    
КонецПроцедуры

Способ 3. Вспомогательная таблица (Master-Detail)

Рассмотрим ситуацию, когда складов очень много. Выводить 50 колонок в таблицу неудобно — пользователю придется постоянно пользоваться горизонтальной прокруткой. В этом случае лучше применить архитектуру "Мастер-Деталь".

Посмотрим, как это работает:

  1. Основной динамический список отображает только номенклатуру и общий остаток.
  2. Рядом или снизу размещается вторая таблица (это может быть ТаблицаЗначений или другой динамический список).
  3. В обработчике события ПриАктивизацииСтроки основного списка мы обновляем данные во второй таблице, показывая расшифровку по складам только для одного выбранного товара.

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

Важные технические моменты

При реализации любого из этих методов следует учитывать несколько критически важных нюансов:

Выбор конкретного решения зависит от ваших бизнес-требований. Если складов 3-5, используйте простое изменение запроса. Если складов много и важна скорость — ваш выбор ПриПолученииДанныхНаСервере. Если же требуется детальная аналитика — используйте вспомогательную таблицу.

← На главную