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