Как правильно спроектировать и реализовать форму подбора номенклатуры с остатками в 1С 8.3

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

Разработка интерактивных интерфейсов в платформе «1С:Предприятие 8.3» на управляемых формах требует от разработчика понимания клиент-серверной архитектуры, принципов работы с динамическими списками и асинхронных вызовов. Одной из наиболее частых задач является создание удобной формы подбора товаров (номенклатуры) для документов резервирования, заказов или отгрузок. В этой статье мы подробно разберем, как спроектировать такую форму с нуля, вывести актуальные остатки товаров на складах в реальном времени, реализовать удобный интерактивный выбор (включая множественный выбор и drag-and-drop), а также безопасно передать данные в документ без использования устаревших модальных окон.

Общая архитектура взаимодействия форм при подборе

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

Существует два основных сценария возврата данных:

  1. Через событие выбора таблицы формы: При открытии формы подбора в качестве ее владельца указывается конкретный элемент формы документа (например, табличная часть Элементы.Товары). В этом случае после вызова метода ОповеститьОВыборе управление передается в обработчик события таблицы ОбработкаВыбора.
  2. Через событие выбора самой формы: Владельцем формы подбора назначается сама форма документа (ЭтаФорма). Тогда событие выбора обрабатывается в процедуре модуля формы документа ОбработкаВыбора.

Мы подробно рассмотрим первый сценарий как наиболее интуитивный и разделяемый на уровне интерфейса.

Шаг 1. Создание и настройка интерфейса формы подбора

Для реализации подбора нам потребуется создать произвольную форму. Назовем ее ФормаПодбора. Нам необходимо разместить на ней два ключевых элемента:

  1. Таблица доступных товаров (каталог): Здесь пользователь видит номенклатуру, характеристики, серии и текущие свободные остатки на складе. Лучшим решением для реализации этого элемента является Динамический список. Он обеспечивает высокую скорость работы, пагинацию «из коробки» и возможность гибкой настройки запроса.
  2. Таблица выбранных товаров (корзина): Сюда пользователь переносит позиции двойным кликом или перетаскиванием, указывает необходимое количество и проверяет итоговый список перед переносом в документ. Для корзины используется реквизит формы с типом ТаблицаЗначений (в контексте формы — ДанныеФормыКоллекция).

Для таблицы доступных товаров обязательно включим свойство Множественный выбор в свойствах элемента формы таблицы. Это позволит пользователю выделять несколько строк удерживанием клавиш Shift или Ctrl и переносить их в корзину одним действием.

Шаг 2. Динамический вывод остатков на складах

Чтобы пользователь видел актуальные остатки, настроим динамический список (назовем реквизит Товары) с произвольным запросом. В запросе мы соединим справочник номенклатуры с виртуальной таблицей остатков регистра накопления.

Рассмотрим пример запроса для динамического списка:


ВЫБРАТЬ
    СправочникНоменклатура.Ссылка КАК Товар,
    СправочникНоменклатура.Код КАК Код,
    ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК ОстатокНаСкладе,
    ЕСТЬNULL(ТоварыНаСкладахОстатки.Серия, ЗНАЧЕНИЕ(Справочник.СерииНоменклатуры.ПустаяСсылка)) КАК Серия
ИЗ
    Справочник.Номенклатура КАК СправочникНоменклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки
    ПО ТоварыНаСкладахОстатки.Номенклатура = СправочникНоменклатура.Ссылка
ГДЕ
    НЕ СправочникНоменклатура.ЭтоГруппа

Обратите внимание на параметр запроса &Склад. При открытии формы подбора мы должны передать склад из документа-владельца в параметры формы подбора и установить его значение в динамическом списке. Сделаем это в обработчике создания формы на сервере:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Проверяем наличие переданного склада в параметрах открытия формы
    Если Параметры.Свойство("Склад") Тогда
        Склад = Параметры.Склад;
        // Устанавливаем параметр в динамическом списке "Товары"
        Товары.Параметры.УстановитьЗначениеПараметра("Склад", Склад);
    КонецЕсли;
    
КонецПроцедуры

Шаг 3. Реализация интерактивного выбора и добавления в корзину

Разберем, как обрабатывать действия пользователя при выборе товара из каталога. Для этого мы задействуем событие Выбор таблицы доступных товаров. При двойном клике на строку (или при нажатии Enter) выбранные товары должны добавиться в таблицу ВыбранныеТовары (нашу корзину).

В коде обработчика важно учитывать возможность множественного выбора (когда параметр ВыбраннаяСтрока содержит массив идентификаторов строк, а не одно число):


&НаКлиенте
Процедура ТоварыВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
    
    // Отменяем стандартное открытие карточки номенклатуры по двойному клику
    СтандартнаяОбработка = Ложь;
    
    // Обрабатываем выбор строк
    ДобавитьСтрокиВКорзину(ВыбраннаяСтрока);
    
КонецПроцедуры

Для поддержки интерфейсного механизма Drag-and-Drop (перетаскивание мышью из каталога в корзину) настроим свойства таблиц. У таблицы-источника (каталог) установим свойство РазрешитьНачалоПеретаскивания в значение Истина. У таблицы-приемника (корзина) установим свойство РазрешитьПеретаскивание в значение Истина и создадим обработчик события ПроверкаПеретаскивания и Перетаскивание:


&НаКлиенте
Процедура ВыбранныеТоварыПеретаскивание(Элемент, ПараметрыПеретаскивания, СтандартнаяОбработка, Строка, Поле)
    
    СтандартнаяОбработка = Ложь;
    
    // При перетаскивании значением является массив идентификаторов строк источника
    Если ТипЗнч(ПараметрыПеретаскивания.Значение) = Тип("Массив") Тогда
        ДобавитьСтрокиВКорзину(ПараметрыПеретаскивания.Значение);
    КонецЕсли;
    
КонецПроцедуры

Шаг 4. Обход проблемы модальности при вводе количества

Использование синхронного метода ВвестиЧисло() внутри циклов — частая и грубая ошибка начинающих разработчиков. В современных версиях платформы 1С при отключенной модальности такой вызов приведет к аварийному завершению работы программы.

Существует два архитектурно верных решения этой задачи:

Вариант А (Лучший UI/UX): Пользователь быстро наполняет корзину (таблицу ВыбранныеТовары) двойным кликом. При этом товар добавляется в количестве 1 штуки. После наполнения корзины пользователь редактирует колонку Количество прямо в таблице корзины на форме подбора. Это избавляет от необходимости постоянно реагировать на всплывающие окна ввода.

Вариант Б (Асинхронный ввод): Если бизнес-логика требует обязательного ввода количества сразу при клике на каждую позицию, необходимо использовать асинхронные методы платформы, такие как ПоказатьВводЧисла(). Разберем реализацию этого паттерна подробнее.

Разработаем общий метод добавления строк с асинхронным запросом количества:


&НаКлиенте
Процедура ДобавитьСтрокиВКорзину(МассивСтрок)
    
    Для Каждого ИдентификаторСтроки Из МассивСтрок Цикл
        
        ТекСтрока = Элементы.Товары.ДанныеСтроки(ИдентификаторСтроки);
        Если ТекСтрока = Неопределено Тогда
            Продолжить;
        КонецЕсли;
        
        // Подготавливаем описание оповещения для обработки завершения ввода числа
        КонтекстВызова = Новый Структура;
        КонтекстВызова.Вставить("Товар", ТекСтрока.Товар);
        КонтекстВызова.Вставить("Серия", ТекСтрока.Серия);
        КонтекстВызова.Вставить("Остаток", ТекСтрока.ОстатокНаСкладе);
        
        ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВводаКоличества", ЭтотОбъект, КонтекстВызова);
        
        // Показываем немодальное окно ввода числа
        ПоказатьВводЧисла(ОписаниеОповещения, 1, "Укажите количество для: " + Строка(ТекСтрока.Товар), 10, 3);
        
    КонецЦикла;
    
КонецПроцедуры

&НаКлиенте
Процедура ПослеВводаКоличества(РезультатЧисло, ДополнительныеПараметры) Экспорт
    
    // Если пользователь закрыл окно или ввел некорректное число, прерываем процесс
    Если РезультатЧисло = Неопределено Или РезультатЧисло <= 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Проверяем превышение остатка на складе
    Если РезультатЧисло > ДополнительныеПараметры.Остаток Тогда
        ПоказатьПредупреждение(, "Введенное количество превышает свободный остаток на складе!");
        Возврат;
    КонецЕсли;
    
    // Выполняем поиск позиции в корзине
    Поиск = Новый Структура("Товар, Серия", ДополнительныеПараметры.Товар, ДополнительныеПараметры.Серия);
    МассивСтрокКорзины = ВыбранныеТовары.НайтиСтроки(Поиск);
    
    Если МассивСтрокКорзины.Количество() = 0 Тогда
        // Добавляем новую позицию в корзину
        НоваяСтрока = ВыбранныеТовары.Добавить();
        НоваяСтрока.Товар = ДополнительныеПараметры.Товар;
        НоваяСтрока.Серия = ДополнительныеПараметры.Серия;
        НоваяСтрока.Количество = РезультатЧисло;
    Иначе
        // Обновляем количество в существующей строке
        МассивСтрокКорзины[0].Количество = РезультатЧисло;
    КонецЕсли;
    
КонецПроцедуры

Шаг 5. Безопасная передача данных в форму документа

Передача клиентского объекта ДанныеФормыКоллекция (которым является наша корзина ВыбранныеТовары на клиенте) напрямую в метод ОповеститьОВыборе часто вызывает критические ошибки платформы, связанные с сериализацией мутабельных типов данных. Для стабильной работы нам необходимо преобразовать коллекцию формы в простой массив структур, содержащий только элементарные типы данных и ссылки.

Реализуем процедуру отправки данных при нажатии на кнопку «Перенести в документ»:


&НаКлиенте
Процедура ПеренестиВДокумент(Команда)
    
    // Формируем массив сериализуемых структур из таблицы выбранных товаров
    МассивДанныхВыбора = Новый Массив;
    
    Для Каждого СтрокаКорзины Из ВыбранныеТовары Цикл
        
        ДанныеСтроки = Новый Структура;
        ДанныеСтроки.Вставить("Товар", СтрокаКорзины.Товар);
        ДанныеСтроки.Вставить("Серия", СтрокаКорзины.Серия);
        ДанныеСтроки.Вставить("Количество", СтрокаКорзины.Количество);
        
        МассивДанныхВыбора.Добавить(ДанныеСтроки);
        
    КонецЦикла;
    
    // Оповещаем форму-владельца и передаем подготовленный массив
    ОповеститьОВыборе(МассивДанныхВыбора);
    
КонецПроцедуры

Если предполагается перенос сотен или тысяч строк, выгрузка на клиенте может выполняться медленно. В таких случаях эффективнее свернуть данные на сервере формы подбора, поместить таблицу значений во временное хранилище и вернуть в вызывающую форму только уникальный текстовый адрес хранилища (АдресХранилища).

Шаг 6. Получение и обработка данных в форме документа

Теперь вернемся к форме нашего документа БронированиеНоменклатуры. Чтобы открыть форму подбора и передать туда контекст (склад), напишем обработчик для команды «Подбор» на форме документа:


&НаКлиенте
Процедура Подбор(Команда)
    
    ПараметрыПодбора = Новый Структура;
    ПараметрыПодбора.Вставить("Склад", Объект.Склад);
    
    // Важно: третьим параметром указываем элемент-владелец (табличную часть формы документа)
    ОткрытьФорму(
        "Документ.БронированиеНоменклатуры.Форма.ФормаПодбора", 
        ПараметрыПодбора, 
        Элементы.Товары
    );
    
КонецПроцедуры

Поскольку владельцем формы подбора мы указали элемент управления Элементы.Товары, платформа автоматически направит возвращаемые данные в событие ОбработкаВыбора этой таблицы. Создадим соответствующий обработчик в модуле формы документа:


&НаКлиенте
Процедура ТоварыОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    
    // Отменяем стандартное поведение платформы по обработке выбора
    СтандартнаяОбработка = Ложь;
    
    // Проверяем тип пришедших данных
    Если ТипЗнч(ВыбранноеЗначение) = Тип("Массив") И ВыбранноеЗначение.Количество() > 0 Тогда
        ЗаполнитьДокументПоРезультатамПодбора(ВыбранноеЗначение);
    КонецЕсли;
    
КонецПроцедуры

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


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

Заключение и лучшие практики

Мы детально разобрали сквозной пример разработки формы подбора. Для того чтобы ваш код всегда соответствовал современным стандартам разработки 1С, рекомендуем придерживаться следующих правил:

← На главную