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

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

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

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

Разберем начальные ошибки: контекст выполнения и дублирование процедур

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

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

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

Посмотрим на первый вариант рабочего решения

После исправления синтаксических ошибок разработчик обычно приходит к коду, который технически работает. Рассмотрим пример такого решения:


&НаКлиенте 
Процедура ПолучитьМашину(Команда) 
    ПолучитьМашинуНаСервере(); 
КонецПроцедуры 

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

    РезультатЗапроса = Запрос.Выполнить(); 
    ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать(); 

    Если ВыборкаДетальныеЗаписи.Следующий() Тогда 
        Объект.Машина = ВыборкаДетальныеЗаписи.Машины; 
    КонецЕсли; 
КонецПроцедуры

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

Проанализируем архитектурные недочеты и оптимизируем код

Давайте по шагам разберем, что именно можно и нужно улучшить в представленном выше коде.

1. Работа с новыми (незаписанными) документами

В коде используется параметр Объект.Ссылка. Главная проблема такого подхода заключается в том, что если пользователь создаст новый документ, выберет контрагента и нажмет кнопку «Получить машину» до записи документа, код не сработает. У нового документа Объект.Ссылка пустая (документа еще нет в базе данных), и запрос к таблице документов ничего не найдет. Поскольку контрагент уже выбран на форме, в запрос нужно передавать не ссылку на документ, а самого контрагента (Объект.Контрагенты), и обращаться напрямую к регистру сведений.

2. Оптимизация работы с виртуальными таблицами

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

3. Использование даты документа

Для документов обычно нужно получать срез не на текущую дату, а на дату документа (Объект.Дата). Если мы не укажем параметр периода, то при перепроведении старого документа может подтянуться современная (неактуальная для того времени) машина.

4. Стандарты именования реквизитов

Обратим внимание на имя реквизита Контрагенты. Согласно стандартам разработки 1С, имена реквизитов, содержащих одно значение (ссылку на один элемент справочника), должны писаться в единственном числе: Контрагент, Организация, Склад. Множественное число используется только для табличных частей (например, Товары) или коллекций. Соблюдение этого правила делает код читаемым.

Итоговое оптимальное решение

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


&НаКлиенте
Процедура ПолучитьМашину(Команда)
    ПолучитьМашинуНаСервере();
КонецПроцедуры

&НаСервере
Процедура ПолучитьМашинуНаСервере()
    
    Если Не ЗначениеЗаполнено(Объект.Контрагент) Тогда
        Возврат; // Если контрагент не выбран, запрос выполнять бессмысленно
    КонецЕсли;

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

Применяя этот подход, мы получаем компактный, быстродействующий и надежный код. Мы учли работу с незаписанными документами, обезопасили базу от построения полных срезов регистра и корректно разделили контексты &НаКлиенте и &НаСервере.

← На главную