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