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