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