Как в табличной части управляемой формы 1С получить реквизит элемента справочника?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Торговля и дистрибуция
← На главную

Часто перед разработчиком встает задача: в табличной части документа на управляемой форме нужно получить доступ к данным связанного объекта. Например, в строке табличной части есть реквизит Запчасть (тип СправочникСсылка.Автозапчасти), и нам нужно проверить значение его реквизита НеПродавать. Попытка сделать это напрямую на клиенте кодом вида Строка.Запчасть.НеПродавать не увенчается успехом. Давайте разберемся, почему так происходит и какие существуют правильные способы решения этой задачи.

Причина такого поведения кроется в клиент-серверной архитектуре управляемых форм. На клиентскую сторону (в браузер или тонкий клиент) платформа передает только самые необходимые данные для отображения формы. Для реквизита типа "ссылка" это и есть сама ссылка, а не все данные объекта, на который она указывает. Чтобы получить полные данные объекта, включая его реквизиты, необходимо сделать явный запрос на сервер, где есть доступ ко всей базе данных.

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

Решение 1: Прямой вызов сервера в цикле

Это самый очевидный способ, который приходит в голову в первую очередь. Мы можем перебрать строки табличной части на клиенте и для каждой строки вызывать серверную функцию, которая вернет нам нужное значение реквизита.

Проанализируем пример кода. Допустим, нам нужно в цикле проверить флаг НеПродавать у каждой запчасти.

Клиентская процедура:


&НаКлиенте
Процедура ПроверитьЗапчасти()
    Для Каждого СтрокаЗапчасти Из Объект.Автозапчасти Цикл 
        // Вызываем серверную функцию для получения значения реквизита
        Если ЗапчастьНеПродаватьПолучить(СтрокаЗапчасти.Запчасть) Тогда 
            Сообщить("Запчасть '" + СтрокаЗапчасти.Запчасть + "' запрещена к продаже!");
            // Какая-то логика...
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Серверная функция:


&НаСервереБезКонтекста
Функция ЗапчастьНеПродаватьПолучить(СсылкаНаЗапчасть)
    // На сервере мы можем "разразыменовать" ссылку и получить доступ к ее реквизитам
    Возврат СсылкаНаЗапчасть.НеПродавать;
КонецФункции

Важный момент: Этот подход прост в реализации, но имеет серьезный недостаток — производительность. Если в табличной части 100 строк, будет выполнено 100 отдельных вызовов на сервер. Каждый вызов — это сетевая задержка и дополнительная нагрузка. На медленных соединениях или при большом объеме данных форма может ощутимо "тормозить". Этот метод подходит для небольших таблиц или разовых проверок, но его следует избегать в высоконагруженных операциях.

Решение 2: Оптимизированный запрос данных (один серверный вызов)

Чтобы избежать множественных серверных вызовов, давайте получим все необходимые данные за один раз. Логика следующая: собираем все интересующие нас ссылки на клиенте, передаем их массивом на сервер, на сервере одним запросом получаем данные для всех ссылок и возвращаем результат на клиент.

Разберем по шагам:

  1. На клиенте: Создаем массив ссылок из табличной части.
  2. На клиенте: Вызываем одну серверную функцию, передавая в нее этот массив.
  3. На сервере: Выполняем запрос к базе данных, используя условие ГДЕ Ссылка В (&МассивСсылок).
  4. На сервере: Возвращаем результат в удобной для клиента структуре, например, в Соответствии, где ключ — это ссылка, а значение — искомый реквизит.
  5. На клиенте: Обрабатываем табличную часть, получая данные уже не с сервера, а из локальной структуры данных.

Посмотрим на пример кода:

Клиентская процедура:


&НаКлиенте
Процедура ПроверитьЗапчастиОптимизированно()
    // Шаг 1: Собираем массив ссылок
    МассивЗапчастей = Объект.Автозапчасти.ВыгрузитьКолонку("Запчасть");
    
    // Шаг 2: Выполняем один серверный вызов
    СоответствиеСтатусов = ПолучитьСтатусыЗапчастейНаСервере(МассивЗапчастей);
    
    // Шаг 5: Обрабатываем данные на клиенте
    Для Каждого СтрокаЗапчасти Из Объект.Автозапчасти Цикл
        Если СоответствиеСтатусов.Получить(СтрокаЗапчасти.Запчасть) Тогда
            Сообщить("Запчасть '" + СтрокаЗапчасти.Запчасть + "' запрещена к продаже!");
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Серверная функция:


&НаСервереБезКонтекста
Функция ПолучитьСтатусыЗапчастейНаСервере(МассивЗапчастей)
    // Шаг 3-4: Выполняем запрос и формируем результат
    Результат = Новый Соответствие;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    Автозапчасти.Ссылка,
        |    Автозапчасти.НеПродавать
        |ИЗ
        |    Справочник.Автозапчасти КАК Автозапчасти
        |ГДЕ
        |    Автозапчасти.Ссылка В(&МассивЗапчастей)";
        
    Запрос.УстановитьПараметр("МассивЗапчастей", МассивЗапчастей);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        Результат.Вставить(Выборка.Ссылка, Выборка.НеПродавать);
    КонецЦикла;
    
    Возврат Результат;
КонецФункции

Этот подход значительно производительнее и является рекомендуемым для работы с коллекциями данных на форме.

Решение 3: Предотвращение выбора некорректных данных

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

Отбор можно установить статически в конфигураторе, но чаще его нужно настраивать динамически при открытии формы. Сделаем это в обработчике события формы ПриСозданииНаСервере.


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Получаем элемент управления - колонку "Запчасть" в табличной части "Автозапчасти"
    // Устанавливаем отбор: показывать только те элементы, где "НеПродавать" = Ложь
    
    Отбор = Новый Структура("НеПродавать", Ложь);
    Элементы.АвтозапчастиЗапчасть.ПараметрыВыбора.Вставить("Отбор", Отбор);
КонецПроцедуры

Теперь при нажатии на кнопку выбора в колонке Запчасть форма выбора справочника откроется с уже установленным фильтром, и пользователь просто не увидит запчасти, запрещенные к продаже. Это значительно улучшает пользовательский опыт (юзабилити).

Решение 4: Визуальное информирование через условное оформление

Что если "запрещенная" запчасть уже была добавлена в документ ранее (например, до того, как у нее установили флаг НеПродавать)? В этом случае ее стоит визуально выделить, чтобы привлечь внимание пользователя. Для этого идеально подходит механизм условного оформления.

Сложность в том, что условное оформление формы может работать только с данными, доступными в этой форме. А реквизита НеПродавать в нашей табличной части нет. Чтобы это обойти, необходимо:

  1. Добавить в табличную часть формы (не в объект!) дополнительную колонку, например, ЗапрещенаКПродаже (тип Булево). Скройте ее от пользователя, она будет служебной.
  2. При чтении данных на сервере (например, в событии ПриЧтенииНаСервере) или с помощью оптимизированного запроса (как в Решении 2) заполнить эту колонку значениями.
  3. Настроить условное оформление. Указать, что если значение в служебной колонке ЗапрещенаКПродаже равно Истина, то нужно, например, изменить цвет фона всей строки документа.

Это более сложный, но очень наглядный для пользователя способ информирования.

Решение 5: Финальная проверка на сервере перед записью

Это обязательный шаг! Любые проверки на клиенте служат только для удобства пользователя. Их можно обойти. Единственным надежным гарантом целостности данных является проверка на сервере в транзакции записи объекта.

В модуле объекта документа, в обработчике события ПередЗаписью, необходимо добавить финальную проверку всей табличной части. Если будут найдены некорректные строки, запись документа следует отменить.


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

Такая проверка гарантирует, что в базу данных не попадут некорректные документы, вне зависимости от того, как они были созданы — интерактивно пользователем или программной загрузкой.

Подводя итог, можно сказать, что для решения исходной задачи лучше всего использовать комбинацию рассмотренных подходов: ограничить выбор (Решение 3), визуально подсветить уже добавленные некорректные данные (Решение 4), используя оптимизированное получение данных (Решение 2), и обязательно реализовать финальную серверную проверку (Решение 5).

← На главную