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