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