Как установить значение реквизита в зависимости от родительской группы другого реквизита в 1С 8.3?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Промышленность, строительство и АПК
← На главную

В процессе разработки конфигураций на платформе 1С:Предприятие 8.3 начинающие программисты часто сталкиваются с задачей автоматического заполнения одних реквизитов формы на основании изменения других. Типичный пример — учебная задача из сферы швейного производства: в документе необходимо автоматически установить цену пошива в зависимости от выбранного материала. Справочник "Ткани" имеет иерархическую структуру, где все элементы разделены на две группы: "Простые" и "Сложные". Если пользователь выбирает простую ткань, в табличной части должна заполниться "Цена 1", если сложную — "Цена 2".

В этой статье мы подробно разберем ошибки, которые допускают новички при реализации подобной логики, рассмотрим правильный механизм работы с типами данных и иерархией справочников, а также разработаем оптимальное с точки зрения производительности решение, соответствующее стандартам разработки фирмы 1С.

Разбор типичных ошибок начинающих разработчиков

Рассмотрим исходный код, который часто пытаются написать начинающие программисты при решении этой задачи:


&НаКлиенте
Процедура ОсновноеИзделиеТканьПриИзменении(Элемент)
    ТекСтрока = Элемент.Родитель.ТекущиеДанные;
    ЗначениеРеквизита = ПолучитьГруппуТканиНаСервере(ТекСтрока.Ткань);
    Если ЗначениеРеквизита = "Сложные" Тогда 
       ТекСтрока.ЦенаПошива = ПолучитьЦенуПошива2НаСервере(ТекСтрока.Наименование);
    КонецЕсли;
КонецПроцедуры

&НаСервере
Функция ПолучитьГруппуТканиНаСервере(Ткани)
    Возврат Ткани.Родитель;
КонецФункции

Проанализируем данную ситуацию и выясним, почему этот код не будет работать или будет работать некорректно. Здесь кроются сразу несколько серьезных ошибок:

  1. Несоответствие типов данных при сравнении. В процедуре сравнения мы видим условие: Если ЗначениеРеквизита = "Сложные" Тогда. Переменная ЗначениеРеквизита получает значение из функции ПолучитьГруппуТканиНаСервере(), которая возвращает Ткани.Родитель. Реквизит Родитель — это ссылка на элемент справочника (тип данных СправочникСсылка.Ткани). В условии же эта ссылка сравнивается со строкой "Сложные". Платформа 1С никогда не приравняет объект ссылки к обычному тексту, из-за чего условие всегда будет ложным.
  2. Обращение к реквизитам объекта через точку на клиенте. Платформа 1С:Предприятие использует разделение на клиентский и серверный контексты. На клиенте нам доступны только данные формы. Мы не можем обратиться "через точку" к реквизитам ссылочных типов (например, попытаться получить наименование родителя или цену напрямую из ссылки). Любая попытка написать на клиенте что-то вроде ТекСтрока.Ткань.Родитель.Наименование приведет к ошибке выполнения. Получение этих данных должно происходить строго на сервере.
  3. Избыточные серверные вызовы. В представленном коде для обработки одного события изменения ткани выполняются последовательно два серверных вызова: сначала функция ПолучитьГруппуТканиНаСервере, а затем (если условие выполнится) функция ПолучитьЦенуПошива2НаСервере. Каждый переход с клиента на сервер — это ресурсоемкая операция, которая замедляет работу интерфейса программы.

Решение проблемы "в лоб" (исправление ошибок сравнения)

Для того чтобы исправить самую главную ошибку — сравнение разных типов данных, нам необходимо сделать так, чтобы сервер возвращал не ссылку на родителя, а его строковое представление (наименование). Давайте посмотрим на пример простого исправления:


&НаСервере
Функция ПолучитьГруппуТканиНаСервере(Ткани)
    // Возвращаем строковое наименование родительской группы
    Возврат Ткани.Родитель.Наименование;
КонецФункции

Теперь функция возвращает тип данных Строка (например, "Сложные" или "Простые"). После этого сравнение на клиенте Если ЗначениеРеквизита = "Сложные" Тогда отработает корректно. Однако такой подход все еще далек от идеала с точки зрения архитектуры и производительности 1С.

Оптимальное архитектурное решение задачи

Разберем по шагам, как реализовать данную задачу профессионально, избегая "костылей" и лишней нагрузки на систему.

Шаг 1. Избавление от жесткого кодирования (Hardcode)

Завязываться в программном коде на текстовое наименование группы ("Сложные") крайне опасно. Если обычный пользователь переименует эту группу в справочнике (например, напишет "Сложные ткани" или случайно поставит лишний пробел в конце), логика программы тут же сломается. Чтобы этого избежать, используют один из двух подходов:

Шаг 2. Минимизация вызовов сервера (Один круглый поход)

Вместо того чтобы гонять данные между клиентом и сервером несколько раз, мы должны передать на сервер все необходимые для анализа параметры, выполнить там расчет и вернуть на клиент уже готовый результат (цену пошива). Для этого мы напишем одну функцию с директивой компиляции &НаСервереБезКонтекста. Использование бесконтекстного вызова экономит сетевой трафик, так как на сервер не передаются все данные текущей формы.

Шаг 3. Использование запросов вместо чтения объектов через точку

Когда мы пишем на сервере Ткань.Родитель.Наименование, платформа выполняет скрытую и неявную выборку из базы данных всего объекта "Ткани" и всего объекта его родителя. Чтобы получить всего одно свойство, система считывает кучу ненужной информации. Правильное решение — использовать объект Запрос. Запрос извлекает из базы данных только те конкретные поля, которые нам необходимы в данный момент.

Шаг 4. Учет многоуровневой иерархии

Обращение напрямую к свойству Родитель сработает только в том случае, если у нас строго двухуровневый справочник. Если внутри группы "Сложные" появится подгруппа "Шелковые", а в ней уже будет лежать элемент "Атлас", то родителем "Атласа" станет группа "Шелковые". Условие сравнения по наименованию родителя нарушится. Чтобы этого избежать, в запросах 1С используется конструкция В ИЕРАРХИИ, которая проверяет вхождение элемента в группу на любом уровне вложенности.

Реализация готового кода

Проанализируем и внедрим оптимальный вариант программного кода в модуль формы документа. Предположим, что папки "Простые" и "Сложные" у нас созданы как предопределенные элементы справочника "Ткани".

Код обработчика события изменения ткани на клиенте:


&НаКлиенте
Процедура ОсновноеИзделиеТканьПриИзменении(Элемент)
    
    // Получаем строку табличной части, в которой произошло изменение ткани
    СтрокаТЧ = Элементы.ОсновноеИзделие.ТекущиеДанные;
    
    Если СтрокаТЧ = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Запускаем расчет цены на сервере за ОДИН бесконтекстный вызов
    СтрокаТЧ.ЦенаПошива = ПолучитьЦенуПошиваНаСервере(СтрокаТЧ.Ткань, СтрокаТЧ.Наименование);
    
КонецПроцедуры

Функция расчета цены на сервере (без контекста формы):


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

Рассмотрим подробнее преимущества данного кода:

← На главную