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