Как автоматически подставить элемент из другого справочника в реквизит формы в 1С:Предприятии 8.3 (Управляемые формы)?

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

При работе с информационными системами на платформе 1С:Предприятие 8.3 мы часто сталкиваемся с необходимостью автоматизировать заполнение данных. Одна из таких задач – это подстановка элементов из одного справочника в реквизит другого объекта, основываясь на данных, полученных из связанного элемента. Например, требуется, чтобы при выборе контрагента в документе, автоматически заполнялся реквизит, представляющий структурную единицу, наименование которой совпадает с полем «Ответственный» из карточки контрагента.

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

Анализ исходной проблемы

Рассмотрим типовую ситуацию, с которой сталкиваются разработчики, особенно переходящие с более старых версий платформы или осваивающие управляемые формы. Автор вопроса столкнулся с проблемой, когда:

  1. При изменении реквизита Контрагент в документе, он хотел получить значение поля Ответственный из этого контрагента.
  2. Использовать это значение (которое является строкой) для поиска соответствующего элемента в справочнике СтруктурныеЕдиницы по наименованию.
  3. Присвоить найденный элемент реквизиту СтруктурнаяЕдиница документа.

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


&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
    
    //Сообщить(НайтиСтруктурнуюЕдиницу(Контрагент));
    //СтруктурнаяЕдиница = НайтиСтруктурнуюЕдиницу(Контрагент);
КонецПроцедуры

&НаСервере
Функция НайтиСтруктурнуюЕдиницу(СтрЕд)
    
    Перем СтрЕдДляПоиска, СсылкаСтрЕд;
    СтрЕдДляПоиска = Справочники.СтруктурныеЕдиницы;
    КонтрагентСтрЕд = Контрагент.Ответственный;
    СсылкаСтрЕд = СтрЕдДляПоиска.НайтиПоНаименованию(КонтрагентСтрЕд);
    Если СсылкаСтрЕд = СтрЕдДляПоиска.ПустаяСсылка() Тогда
        Возврат "Контрагент не найден";
    Иначе
        Возврат СсылкаСтрЕд.Наименование;
    КонецЕсли;
    
КонецФункции

Мы видим, что в клиентской процедуре КонтрагентПриИзменении вызывается серверная функция НайтиСтруктурнуюЕдиницу. При этом, автор справедливо отметил, что команда Сообщить(НайтиСтруктурнуюЕдиницу(Контрагент)); выводит правильное наименование, но закомментированная строка СтруктурнаяЕдиница = НайтиСтруктурнуюЕдиницу(Контрагент); не работает, и реквизит СтруктурнаяЕдиница остаётся пустым.

Выясняем причину: Типы данных и клиент-серверное взаимодействие

  1. Несоответствие типов данных: Главная причина, по которой реквизит остаётся пустым, заключается в несоответствии типов данных. Реквизит формы СтруктурнаяЕдиница, скорее всего, имеет тип СправочникСсылка.СтруктурныеЕдиницы. То есть он ожидает получить ссылку на элемент справочника. Однако, серверная функция НайтиСтруктурнуюЕдиницу в строке Возврат СсылкаСтрЕд.Наименование; возвращает не ссылку, а строку (наименование найденного элемента). Система 1С не может автоматически преобразовать строку в ссылку на элемент справочника при прямом присваивании, если это не специальное поле ввода с механизмом выбора. Поэтому присвоение строки реквизиту типа "ссылка" приводит к тому, что реквизит остаётся пустым или не заполняется корректно.
  2. Контекст выполнения кода: В управляемых формах 1С существует строгое разделение между кодом, выполняемым &НаКлиенте (на компьютере пользователя), и кодом, выполняемым &НаСервере (на сервере 1С).
    • Код &НаКлиенте: Отвечает за интерфейс, обработку событий формы, взаимодействие с пользователем. Он не имеет прямого доступа к данным базы (справочникам, документам), кроме тех, которые уже загружены в форму.
    • Код &НаСервере: Выполняется на сервере, имеет полный доступ к базе данных, позволяет выполнять запросы, получать объекты, записывать данные. Все операции, связанные с поиском или изменением данных в базе, должны выполняться на сервере.

    В нашем случае, функция НайтиСтруктурнуюЕдиницу правильно размещена на сервере, так как она обращается к справочникам (Справочники.СтруктурныеЕдиницы). Однако, проблема была в том, как результат использовался.

Решение проблемы: Пошаговый подход

Для корректного решения задачи нам необходимо:

  1. Вызвать серверную процедуру из клиентской, передав ей необходимую информацию.
  2. На сервере получить все нужные данные.
  3. На сервере найти элемент справочника.
  4. На сервере присвоить найденную ссылку реквизиту объекта формы.

Шаг 1: Правильная передача данных и вызов на сервере

Начнем с клиентской процедуры КонтрагентПриИзменении. Она должна вызывать серверную процедуру для выполнения всей логики. Мы передадим ссылку на выбранного контрагента, чтобы сервер мог с ней работать.


&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
    // Получаем текущее значение контрагента из реквизита объекта формы.
    // Важно: Объект.Контрагент содержит ССЫЛКУ на контрагента.
    КонтрагентСсылка = Объект.Контрагент; 
    
    // Вызываем серверную процедуру для заполнения реквизита "СтруктурнаяЕдиница".
    // Передаем ссылку на контрагента в серверную процедуру.
    ЗаполнитьСтруктурнуюЕдиницуНаСервере(КонтрагентСсылка);
КонецПроцедуры

Здесь мы используем Объект.Контрагент, чтобы получить текущее значение выбранного контрагента. Объект – это объект данных, связанный с формой (например, документ). Затем мы вызываем новую серверную процедуру ЗаполнитьСтруктурнуюЕдиницуНаСервере, передавая ей эту ссылку.

Шаг 2: Обработка на сервере и присвоение ссылки

Теперь создадим серверную процедуру ЗаполнитьСтруктурнуюЕдиницуНаСервере. Она будет выполнять всю основную логику:

  1. Проверит, заполнена ли ссылка на контрагента.
  2. Получит значение реквизита Ответственный из контрагента.
  3. Найдет соответствующий элемент в справочнике СтруктурныеЕдиницы.
  4. Присвоит найденную ссылку реквизиту Объект.СтруктурнаяЕдиница.

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

    // Для того чтобы получить реквизит "Ответственный" из контрагента,
    // нам нужно "развернуть" ссылку в объект.
    // Получать объект по ссылке – это операция, требующая обращения к базе данных.
    ОбъектКонтрагента = КонтрагентСсылка.ПолучитьОбъект();
    
    // Получаем наименование ответственного. Это будет строка.
    НаименованиеДляПоиска = ОбъектКонтрагента.Ответственный;

    // Ищем структурную единицу по этому наименованию в соответствующем справочнике.
    // Метод НайтиПоНаименованию() возвращает ССЫЛКУ на найденный элемент
    // или ПустаяСсылка(), если ничего не найдено.
    НайденнаяСсылка = Справочники.СтруктурныеЕдиницы.НайтиПоНаименованию(НаименованиеДляПоиска);

    // Присваиваем найденное значение (ссылку!) реквизиту объекта формы.
    // Это присваивание происходит на сервере, что является правильным подходом,
    // так как мы работаем с данными объекта, связанного с базой.
    Объект.СтруктурнаяЕдиница = НайденнаяСсылка;
    
    // Дополнительно, если нужно сообщить пользователю о том, что элемент не найден:
    Если НайденнаяСсылка = Справочники.СтруктурныеЕдиницы.ПустаяСсылка() Тогда
        Сообщить("Структурная единица с наименованием '" + НаименованиеДляПоиска + "' не найдена.", СтатусСообщения.Важное);
    КонецЕсли;
КонецПроцедуры

Обратите внимание на ключевые моменты в этой серверной процедуре:

Полный пример рабочего кода

Вот как будет выглядеть окончательный, рабочий код для вашей формы:


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

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

    // Получаем объект контрагента для доступа к его реквизиту "Ответственный".
    ОбъектКонтрагента = КонтрагентСсылка.ПолучитьОбъект();
    НаименованиеДляПоиска = ОбъектКонтрагента.Ответственный;

    // Ищем структурную единицу по наименованию.
    НайденнаяСсылка = Справочники.СтруктурныеЕдиницы.НайтиПоНаименованию(НаименованиеДляПоиска);

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

Оптимизация и лучшие практики

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

Эффективное получение данных: Использование запросов

Использование метода ПолучитьОбъект() для каждой ссылки может быть не самым оптимальным с точки зрения производительности, особенно если в одной форме выполняется множество подобных операций. Каждый вызов ПолучитьОбъект() – это отдельное обращение к базе данных. Более эффективным способом является использование запросов, которые позволяют за одно обращение к базе данных получить все необходимые данные.

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


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

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

    // Устанавливаем значение параметра запроса.
    Запрос.УстановитьПараметр("Контрагент", КонтрагентСсылка);
    
    // Выполняем запрос.
    Результат = Запрос.Выполнить();
    
    // Проверяем, вернул ли запрос какие-либо данные.
    Если Результат.Пустой() Тогда
        Объект.СтруктурнаяЕдиница = Справочники.СтруктурныеЕдиницы.ПустаяСсылка();
        Возврат;
    КонецЕсли;

    // Выбираем данные из результата запроса.
    Выборка = Результат.Выбрать();
    Выборка.Следующий(); // Переходим к первой (и единственной) строке результата.
    
    // Получаем наименование ответственного из выборки запроса.
    НаименованиеДляПоиска = Выборка.Ответственный;
    
    // Ищем структурную единицу по наименованию.
    Объект.СтруктурнаяЕдиница = Справочники.СтруктурныеЕдиницы.НайтиПоНаименованию(НаименованиеДляПоиска);
    
    // Сообщаем пользователю, если структурная единица не найдена.
    Если Объект.СтруктурнаяЕдиница = Справочники.СтруктурныеЕдиницы.ПустаяСсылка() Тогда
        Сообщить("Структурная единица с наименованием '" + НаименованиеДляПоиска + "' не найдена в справочнике.", СтатусСообщения.Важное);
    КонецЕсли;
КонецПроцедуры

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

Обработка неуникальных наименований

Метод НайтиПоНаименованию() имеет один потенциальный недостаток: если в справочнике СтруктурныеЕдиницы существуют несколько элементов с одинаковым наименованием, он всегда вернет ссылку на первый найденный элемент (по внутреннему порядку платформы). Это может привести к некорректному выбору.

Чтобы избежать этой проблемы, мы рекомендуем:

  1. Обеспечить уникальность наименований: В идеале, настройте справочник СтруктурныеЕдиницы так, чтобы наименования были уникальными. Это можно сделать на уровне конфигурации, установив свойство "Проверка уникальности" для реквизита "Наименование".
  2. Использовать другие реквизиты для поиска: Если существует другой, более уникальный реквизит (например, код, ИНН, или специальный идентификатор), лучше использовать метод НайтиПоРеквизиту() или построить запрос с условием по этому уникальному реквизиту.
  3. Уточняющие запросы: Для более сложных случаев, когда критерий поиска многогранен, можно использовать запросы, которые учитывают несколько условий, чтобы точно идентифицировать нужный элемент.

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

Полная проверка на заполненность и пустые ссылки

Очень важно всегда выполнять проверки на заполненность значений и пустые ссылки. Это предотвращает ошибки в работе программы и делает ее более надежной. Мы уже включили эти проверки в наш код:

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

Принципы работы с клиентом и сервером в 1С

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

  1. Клиент вызывает сервер: Клиентский код инициирует действия, которые требуют доступа к базе данных или выполнения сложных расчетов.
  2. Сервер работает с данными: Все операции чтения, записи, поиска, изменения объектов базы данных выполняются исключительно на сервере.
  3. Передача данных: Между клиентом и сервером передаются только значения или ссылки на объекты. Сами объекты, их реквизиты или методы могут быть доступны только в соответствующем контексте.
  4. Синхронизация формы: Изменения, внесенные в Объект (реквизит формы) на сервере, автоматически отображаются на форме после завершения серверного вызова.

Понимание этих принципов критически важно для эффективной разработки на платформе 1С:Предприятие 8.3.

Заключение

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

← На главную