При разработке пользовательских интерфейсов в 1С часто возникает задача реализации удобного механизма подбора. Например, когда пользователю нужно открыть вспомогательное окно, выбрать там необходимые позиции номенклатуры (возможно, сразу указав количество и цену) и перенести их в основную форму документа или обработки. Ситуация усложняется, если данные для подбора формируются динамически (например, сложным запросом) и их нужно корректно передать между формами.
В этой статье мы подробно рассмотрим, как правильно спроектировать и закодировать механизм подбора на управляемых формах в платформе 1С:Предприятие 8.3. Мы разберем передачу данных через временное хранилище, обработку выбора на стороне основной формы, исключение дублирования строк, а также рассмотрим профессиональный паттерн проектирования подбора с использованием "Корзины", аналогичный механизмам в типовых конфигурациях (например, "Управление торговлей 11").
На платформе 1С v8.3 управляемые формы работают в разделенном контексте "Клиент-Сервер". Прямая передача больших таблиц значений между формами "в лоб" через клиентские параметры неэффективна, так как вызывает избыточную сериализацию данных и нагружает сеть. Правильный архитектурный шаблон взаимодействия выглядит следующим образом:
ТаблицаЗначений.Давайте пошагово реализуем этот алгоритм в кодовой базе нашей обработки.
Предположим, на основной форме обработки у нас есть кнопка "Подбор". Напишем обработчик нажатия этой кнопки. Нам потребуется сформировать таблицу значений запросом на сервере, поместить её во временное хранилище и открыть форму подбора.
Обратите внимание на использование свойства формы УникальныйИдентификатор. Передавая его в функцию помещения в хранилище, мы привязываем время жизни этих данных к жизненному циклу самой основной формы.
&НаКлиенте
Процедура КомандаПодбор(Команда)
// Получаем адрес временного хранилища с заполненной ТЗ
АдресДанныхПодбора = ПолучитьДанныеДляПодбораНаСервере();
// Формируем параметры для открытия формы подбора
ПараметрыПодбора = Новый Структура;
ПараметрыПодбора.Вставить("АдресДанных", АдресДанныхПодбора);
// Открываем форму подбора нашей обработки
// Третьим параметром передаем ЭтаФорма, чтобы сработал механизм ОбработкаВыбора
ОткрытьФорму("Обработка.МояОбработка.Форма.ФормаПодбора", ПараметрыПодбора, ЭтаФорма);
КонецПроцедуры
&НаСервере
Функция ПолучитьДанныеДляПодбораНаСервере()
// Формируем запрос для получения номенклатуры, серий и остатков
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
| ТоварыНаСкладахОстатки.Серия КАК Серия,
| ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Количество,
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Склад = &Склад) КАК ТоварыНаСкладахОстатки
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныНоменклатурыСрезПоследних
| ПО ТоварыНаСкладахОстатки.Номенклатура = ЦеныНоменклатурыСрезПоследних.Номенклатура
|ГДЕ
| ТоварыНаСкладахОстатки.ВНаличииОстаток > 0";
Запрос.УстановитьПараметр("Склад", Объект.Склад);
Запрос.УстановитьПараметр("Период", ТекущаяДата());
РезультатЗапроса = Запрос.Выполнить();
ТаблицаДанных = РезультатЗапроса.Выгрузить();
// Помещаем таблицу во временное хранилище
Возврат ПоместитьВоВременноеХранилище(ТаблицаДанных, УникальныйИдентификатор);
КонецФункции
Теперь перейдем к форме подбора (создайте в обработке вторую форму с именем ФормаПодбора). На этой форме нам нужно отобразить полученные данные. Для этого создадим реквизит формы с именем ТаблицаПодбора и типом ТаблицаЗначений. Добавим в эту таблицу колонки: Номенклатура (СправочникСсылка.Номенклатура), Серия (СправочникСсылка.СерииНоменклатуры), Количество (Число), Цена (Число). Вынесем этот реквизит на форму в виде элемента "Таблица".
Чтобы разрешить пользователю выбирать сразу несколько строк с помощью клавиш Ctrl или Shift, обязательно настроим свойство элемента формы таблицы: установим Режим выделения в значение Множественный.
В модуле формы подбора пропишем обработчик события ПриСозданииНаСервере, где извлечем переданные данные:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем наличие параметра с адресом временного хранилища
Если Параметры.Свойство("АдресДанных") Тогда
// Получаем таблицу значений из хранилища
ТаблицаИзХранилища = ПолучитьИзВременногоХранилища(Параметры.АдресДанных);
Если ТипЗнч(ТаблицаИзХранилища) = Тип("ТаблицаЗначений") Тогда
// Заполняем реквизит управляемой формы данными таблицы значений
ЗначениеВРеквизитФормы(ТаблицаИзХранилища, "ТаблицаПодбора");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Когда пользователь отметил нужные строки, он должен нажать кнопку выбора (например, "Перенести в документ"). Напишем обработчик для этой кнопки на клиенте. Мы соберем все выделенные строки в массив структур и передадим его в вызывающую форму с помощью метода ОповеститьОВыборе().
&НаКлиенте
Процедура ПеренестиВОсновнуюФорму(Команда)
МассивРезультата = Новый Массив;
// Обходим массив идентификаторов выделенных строк
Для Каждого ИдентификаторСтроки Из Элементы.ТаблицаПодбора.ВыделенныеСтроки Цикл
СтрокаДанных = Элементы.ТаблицаПодбора.ДанныеСтроки(ИдентификаторСтроки);
Если СтрокаДанных <> Неопределено Тогда
// Формируем структуру с данными для переноса
СтруктураСтроки = Новый Структура;
СтруктураСтроки.Вставить("Номенклатура", СтрокаДанных.Номенклатура);
СтруктураСтроки.Вставить("Серия", СтрокаДанных.Серия);
СтруктураСтроки.Вставить("Количество", СтрокаДанных.Количество);
СтруктураСтроки.Вставить("Цена", СтрокаДанных.Цена);
МассивРезультата.Добавить(СтруктураСтроки);
КонецЕсли;
КонецЦикла;
// Закрываем форму подбора и возвращаем результат
ОповеститьОВыборе(МассивРезультата);
КонецПроцедуры
После вызова метода ОповеститьОВыборе() в основной форме автоматически срабатывает событие ОбработкаВыбора. Нам необходимо перехватить переданный массив строк, обойти его в цикле и добавить в табличную часть обработки (например, Товары). При этом важно проверить: если товар с такой же серией уже присутствует в табличной части, мы не должны плодить дублирующие строки, а просто увеличим количество существующего товара.
Для эффективного поиска дубликатов на сервере мы применим метод НайтиСтроки() табличной части 1С.
&НаКлиенте
Процедура ОбработкаВыбора(РезультатВыбора, ИсточникВыбора)
// Проверяем, что к нам пришел массив строк
Если ТипЗнч(РезультатВыбора) = Тип("Массив") Тогда
// Передаем обработку на сервер для работы с табличной частью
ДобавитьПодобранныеТоварыНаСервере(РезультатВыбора);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ДобавитьПодобранныеТоварыНаСервере(МассивВыбранныхСтрок)
Для Каждого ВыбраннаяСтрока Из МассивВыбранныхСтрок Цикл
// Настраиваем параметры поиска для исключения дублей
ПараметрыПоиска = Новый Структура;
ПараметрыПоиска.Вставить("Номенклатура", ВыбраннаяСтрока.Номенклатура);
ПараметрыПоиска.Вставить("Серия", ВыбраннаяСтрока.Серия);
// Ищем существующие строки в табличной части обработки
НайденныеСтроки = Объект.Товары.НайтиСтроки(ПараметрыПоиска);
Если НайденныеСтроки.Количество() > 0 Тогда
// Если строка найдена, увеличиваем количество и пересчитываем сумму
ТекущаяСтрока = НайденныеСтроки[0];
ТекущаяСтрока.Количество = ТекущаяСтрока.Количество + ВыбраннаяСтрока.Количество;
ТекущаяСтрока.Сумма = ТекущаяСтрока.Количество * ТекущаяСтрока.Цена;
Иначе
// Если строки нет, добавляем новую запись
НоваяСтрока = Объект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ВыбраннаяСтрока);
НоваяСтрока.Сумма = НоваяСтрока.Количество * НоваяСтрока.Цена;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Рассмотрим более эргономичный и современный вариант, упомянутый во втором сообщении форума. В типовых конфигурациях пользователь не просто выделяет строки в общем списке, а переносит их двойным кликом в нижнюю таблицу формы подбора — "Корзину". В корзине пользователь может сразу скорректировать количество и цену, а затем перенести накопленный результат в документ одной кнопкой. Это исключает необходимость многократно открывать и закрывать форму подбора.
Для реализации этого механизма на форме подбора добавим второй реквизит типа ТаблицаЗначений с именем Корзина (с аналогичной структурой колонок) и выведем его на форму ниже основной таблицы.
Настроим событие Выбор (двойной клик) для верхней таблицы подбора:
&НаКлиенте
Процедура ТаблицаПодбораВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
// Отменяем стандартное поведение платформы (открытие карточки номенклатуры)
СтандартнаяОбработка = Ложь;
ТекущиеДанные = Элементы.ТаблицаПодбора.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Переносим строку в корзину
ДобавитьТоварВКорзину(ТекущиеДанные);
КонецПроцедуры
&НаКлиенте
Процедура ДобавитьТоварВКорзину(ДанныеТовара)
// Ищем товар в корзине на клиенте, чтобы избежать лишнего серверного вызова
ПараметрыПоиска = Новый Структура("Номенклатура, Серия", ДанныеТовара.Номенклатура, ДанныеТовара.Серия);
НайденныеСтрокиКорзины = Корзина.НайтиСтроки(ПараметрыПоиска);
Если НайденныеСтрокиКорзины.Количество() > 0 Тогда
// Товар уже в корзине, увеличиваем количество на 1
СтрокаКорзины = НайденныеСтрокиКорзины[0];
СтрокаКорзины.Количество = СтрокаКорзины.Количество + 1;
Иначе
// Добавляем новую строку в корзину
НоваяСтрока = Корзина.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ДанныеТовара);
НоваяСтрока.Количество = 1; // По умолчанию добавляем 1 штуку
КонецЕсли;
КонецПроцедуры
При таком подходе кнопка "Перенести в документ" будет отправлять в основную форму содержимое всей таблицы Корзина целиком, что значительно повышает удобство работы пользователя с большими объемами данных.
&НаКлиенте
Процедура ПеренестиКорзинуВДокумент(Команда)
МассивРезультата = Новый Массив;
Для Каждого СтрокаКорзины Из Корзина Цикл
СтруктураСтроки = Новый Структура("Номенклатура, Серия, Количество, Цена");
ЗаполнитьЗначенияСвойств(СтруктураСтроки, СтрокаКорзины);
МассивРезультата.Добавить(СтруктураСтроки);
КонецЦикла;
ОповеститьОВыборе(МассивРезультата);
КонецПроцедуры
Мы успешно реализовали гибкий и производительный механизм подбора в управляемом приложении 1С 8.3. Давайте резюмируем ключевые правила разработки таких интерфейсов:
ОповеститьОВыборе() на стороне подбора и обработчик ОбработкаВыбора на стороне принимающей формы. Это позволит вам использовать одну и ту же форму подбора в разных документах без переписывания кода.НайтиСтроки() у табличных частей позволяет лаконично и быстро решить эту задачу, сохраняя порядок и точность учета.