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