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