Разработка удобного и быстрого интерфейса подбора товаров — одна из наиболее частых задач при кастомизации конфигураций на платформе «1С:Предприятие 8.3». Часто штатные механизмы выбора номенклатуры не удовлетворяют потребностям бизнеса (например, когда нужно сразу видеть остатки на складах, резервы или цены), и разработчики создают специализированные внешние обработки подбора.
В этой статье мы подробно разберем, как правильно связать форму внешней обработки подбора с формой документа-приемника (например, «Перемещение товаров»), как запрограммировать кнопку «ОК» («Перенести в документ») и как избежать критических архитектурных ошибок, которые могут привести к потере данных пользователей.
Прежде чем перейти к правильным решениям, давайте проанализируем критическую ошибку, которая часто встречается у начинающих разработчиков и была предложена на форуме в Сообщении №2. Автор сообщения рекомендует по кнопке «ОК» получать объект документа из базы данных и напрямую записывать в него строки:
// ОПАСНЫЙ КОД! ТАК ДЕЛАТЬ КАТЕГОРИЧЕСКИ НЕЛЬЗЯ:
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
Строка = ДокументОбъект.Товары.Добавить();
Строка.Номенклатура = ВыбраннаяНоменклатура;
ДокументОбъект.Записать();
Выясним причину, почему этот подход является крайне опасным:
ПолучитьОбъект(), напрямую модифицирует его в базе данных и записывает, то на уровне СУБД данные меняются. Однако форма документа на экране пользователя ничего об этом не знает! Когда пользователь нажмет кнопку «Провести» или «Записать» в форме документа, платформа попытается сохранить состояние формы поверх уже измененных данных СУБД и выдаст ошибку: «Данные были изменены или удалены другим пользователем». В худшем случае изменения из подбора просто затрутся.ДокументОбъект) на клиенте невозможна. Попытка вызвать подобные методы в клиентских процедурах управляемой формы приведет к ошибкам компиляции.Правильное правило разработки: подбор должен передавать данные непосредственно в открытую форму документа-владельца на клиенте, не затрагивая базу данных напрямую. Базу данных должен изменять сам документ при интерактивном сохранении пользователем.
Рассмотрим по шагам три современных и надежных способа реализации этой задачи в управляемом приложении 1С 8.3.
---
Этот способ основан на стандартном механизме оповещения владельца формы. Поскольку напрямую передать сложный объект типа ТаблицаЗначений между клиентскими формами нельзя (возникает ошибка сериализации), мы преобразуем табличную часть обработки в массив простых структур и передадим его в форму документа.
Шаг 1. Программируем кнопку «ОК» в форме внешней обработки подбора
Создадим на клиенте обработчик команды для кнопки «ОК» (или «Перенести в документ»):
&НаКлиенте
Процедура ОК(Команда)
// Создаем массив для хранения строк, который платформа может легко передать на клиент
МассивДанных = Новый Массив;
// Обходим коллекцию строк нашей формы подбора (например, ТЧ "ТоварыДляПодбора")
Для Каждого СтрокаПодбора Из Объект.ТоварыДляПодбора Цикл
// Переносим только строки, где количество больше нуля
Если СтрокаПодбора.Количество > 0 Тогда
СтруктураСтроки = Новый Структура;
СтруктураСтроки.Вставить("Номенклатура", СтрокаПодбора.Номенклатура);
СтруктураСтроки.Вставить("Количество", СтрокаПодбора.Количество);
МассивДанных.Добавить(СтруктураСтроки);
КонецЕсли;
КонецЦикла;
// Передаем собранные данные форме-владельцу и закрываем форму подбора
ОповеститьОВыборе(МассивДанных);
КонецПроцедуры
Шаг 2. Вызываем форму подбора из документа «Перемещение товаров»
Чтобы документ мог поймать возвращаемые данные, при открытии формы обработки мы должны явно указать владельца. В данном случае владельцем будет выступать табличное поле формы документа Элементы.Товары.
&НаКлиенте
Процедура ОткрытьПодбор(Команда)
// Открываем форму внешней обработки подбора.
// Передаем Элементы.Товары (табличное поле документа) в качестве владельца формы
ПараметрыОткрытия = Новый Структура;
ОткрытьФорму("ВнешняяОбработка.ПодборТоваров.Форма.ФормаПодбора", ПараметрыОткрытия, Элементы.Товары);
КонецПроцедуры
Шаг 3. Обрабатываем полученные данные в форме документа
Для элемента формы Товары (табличной части) необходимо создать обработчик события ОбработкаВыбора. В этой процедуре мы примем массив структур и добавим строки на экран:
&НаКлиенте
Процедура ТоварыОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
// Отменяем стандартный выбор платформы
СтандартнаяОбработка = Ложь;
// Убедимся, что к нам пришел массив данных из нашей формы подбора
Если ТипЗнч(ВыбранноеЗначение) = Тип("Массив") Тогда
Для Каждого СтрокаДанных Из ВыбранноеЗначение Цикл
// Добавляем строку непосредственно в текущие данные открытой формы документа
НоваяСтрока = Объект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаДанных);
КонецЦикла;
КонецЕсли;
КонецПроцедуры
---
Если пользователь подбирает сотни или тысячи строк номенклатуры, передача больших массивов структур по сетевому каналу между клиентом и сервером может вызвать просадку производительности. В этом случае идеальным решением будет выгрузить таблицу во временное хранилище на сервере и передать на клиент только короткий адрес-ссылку.
Шаг 1. Код кнопки «ОК» в форме подбора:
&НаКлиенте
Процедура ОК(Команда)
// Передаем выгрузку данных на сервер для помещения в хранилище
АдресХранилища = ПоместитьДанныеВХранилищеНаСервере();
// Передаем клиенту-документу только строку адреса
ОповеститьОВыборе(АдресХранилища);
КонецПроцедуры
&НаСервере
Функция ПоместитьДанныеВХранилищеНаСервере()
// Выгружаем табличную часть формы в полноценную ТаблицуЗначений
ТаблицаДляПереноса = Объект.ТоварыДляПодбора.Выгрузить();
// Помещаем таблицу во временное хранилище, привязывая её к уникальному идентификатору формы подбора
Возврат ПоместитьВоВременноеХранилище(ТаблицаДляПереноса, УникальныйИдентификатор);
КонецФункции
Шаг 2. Код обработки выбора в форме документа «Перемещение товаров»:
&НаКлиенте
Процедура ТоварыОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// Проверяем, что к нам вернулась строка с адресом временного хранилища
Если ТипЗнч(ВыбранноеЗначение) = Тип("Строка") И СтрНачинаетсяС(ВыбранноеЗначение, "e1cib/tempstorage/") Тогда
// Заполняем ТЧ на сервере
ЗаполнитьТабличнуюЧастьИзХранилища(ВыбранноеЗначение);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьТабличнуюЧастьИзХранилища(АдресХранилища)
// Получаем таблицу значений из хранилища
ТаблицаИзПодбора = ПолучитьИзВременногоХранилища(АдресХранилища);
Если ТипЗнч(ТаблицаИзПодбора) = Тип("ТаблицаЗначений") Тогда
Для Каждого СтрокаТЗ Из ТаблицаИзПодбора Цикл
// Добавляем строки в объект формы документа
НоваяСтрока = Объект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЗ);
КонецЦикла;
КонецЕсли;
КонецПроцедуры
---
Если вы хотите сделать механизм более универсальным и не привязывать форму подбора к конкретному элементу (табличному полю) на форме документа, воспользуемся современным асинхронным методом открытия форм через ОписаниеОповещения.
Шаг 1. Открытие формы подбора из документа:
&НаКлиенте
Процедура ОткрытьПодборАсинхронно(Команда)
// Настраиваем процедуру обратного вызова, которая сработает ПОСЛЕ закрытия формы подбора
ПараметрыОповещения = Новый ОписаниеОповещения("ПослеЗакрытияПодбора", ЭтотОбъект);
// Открываем форму, передав владельцем саму форму документа (ЭтотОбъект)
ОткрытьФорму("ВнешняяОбработка.ПодборТоваров.Форма.ФормаПодбора", , ЭтотОбъект, , , , ПараметрыОповещения);
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗакрытияПодбора(РезультатВыбора, ДополнительныеПараметры) Экспорт
// Сюда управление придет автоматически, когда форма подбора закроется
Если РезультатВыбора <> Неопределено Тогда
// РезультатВыбора содержит переданный массив строк
Для Каждого СтрокаДанных Из РезультатВыбора Цикл
НоваяСтрока = Объект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаДанных);
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Шаг 2. Код закрытия в форме подбора (Кнопка ОК):
&НаКлиенте
Процедура ОК(Команда)
МассивДанных = ПолучитьМассивДанныхДляПереноса(); // собираем массив структур
// Закрываем форму подбора и передаем данные напрямую в описание оповещения документа
Закрыть(МассивДанных);
КонецПроцедуры
---
Проанализируем обсуждение в сообщениях форума с №12 по №19. Участники форума абсолютно справедливо заметили, что создание отдельной обработки только ради отображения остатков — это часто избыточное и неоправданное усложнение архитектуры решения.
Если ваша единственная цель — показать пользователю актуальные остатки товаров на складах и быстро заполнить ими документ, то гораздо эффективнее реализовать команду непосредственно внутри формы самого документа «Перемещение товаров».
Для этого можно добавить команду «Заполнить остатками» прямо над табличной частью документа. Давайте посмотрим, насколько проще выглядит этот код:
&НаКлиенте
Процедура ЗаполнитьОстатками(Команда)
// Проверим, заполнен ли склад-отправитель
Если Не ЗначениеЗаполнено(Объект.СкладОтправитель) Тогда
ПоказатьПредупреждение(, "Пожалуйста, выберите склад отправитель!");
Возврат;
КонецЕсли;
// Вызываем серверную процедуру для заполнения остатков напрямую
ЗаполнитьОстаткиНаСервере();
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьОстаткиНаСервере()
// Очищаем текущую табличную часть перед заполнением
Объект.Товары.Очистить();
// Запрос к регистру накопления "ТоварыНаСкладах"
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
| ТоварыНаСкладахОстатки.В НаличииОстаток КАК Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК ТоварыНаСкладахОстатки";
Запрос.УстановитьПараметр("Склад", Объект.СкладОтправитель);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НоваяСтрока = Объект.Товары.Добавить();
НоваяСтрока.Номенклатура = Выборка.Номенклатура;
НоваяСтрока.Количество = Выборка.Количество;
КонецЦикла;
КонецПроцедуры
Такой подход избавляет вас от необходимости поддерживать внешние файлы обработок, исключает ошибки передачи данных по каналам связи «клиент-сервер» и делает интерфейс программы интуитивно понятным для конечного пользователя.