Как получить кадровую историю и свернуть периоды в ЗУП 3

Программист 1С v8.3 (Управляемые формы) 1С:Зарплата и Управление Персоналом IT и автоматизация бизнеса
← На главную

При разработке в конфигурации Зарплата и управление персоналом 3.1 (ЗУП 3) часто возникают две взаимосвязанные задачи: получение среза кадровых данных за произвольный период и преобразование набора ежедневных записей в компактную таблицу интервалов. В этой статье мы подробно разберем, как использовать стандартные механизмы системы и как написать эффективный запрос для группировки периодов.

Использование стандартных функций для получения кадровой истории

Для начала рассмотрим вопрос получения кадровой истории. В ЗУП 3.1 разработчики предусмотрели мощный программный интерфейс, который избавляет от необходимости писать сложные соединения с регистрами вручную. Основным инструментом является общий модуль КадровыйУчет.

Если вам необходимо получить данные на конкретные даты или за период с учетом границ (значение на начало, изменения внутри, значение на конец), рекомендуется использовать функцию КадровыйУчет.ПодготовитьКадровуюИсториюСотрудников. Рассмотрим основные шаги работы с ней:

  1. Подготовка таблицы сотрудников: Создаем временную таблицу, содержащую колонки Сотрудник и Период (дата, на которую нужны данные).
  2. Вызов функции: Функция подготавливает данные во временных таблицах менеджера временных таблиц, учитывая все кадровые приказы.
  3. Чтение результата: Извлекаем данные из созданных временных таблиц (например, ВТКадроваяИсторияСотрудников).

Также полезно обратить внимание на общий модуль ЗарплатаКадрыПериодическиеРегистры. Метод СоздатьВТИмяРегистраПериоды позволяет сформировать таблицу интервалов на основе периодического регистра сведений. Это особенно удобно, когда нужно получить «разрезанную» историю по разным измерениям.

Задача о сворачивании ежедневных записей в интервалы

Представим ситуацию: у нас есть таблица значений, где для каждого дня указан некий признак (например, 1 — сотрудник работал, 0 — отсутствовал). Нам нужно «красиво» собрать эти данные в таблицу периодов «ДатаНачала — ДатаОкончания — Признак».

Разберем логику решения этой задачи (известной как «Острова и промежутки») с помощью запроса. Основная идея заключается в определении границ, где значение признака меняется.

Вариант 1: Поиск границ через левое соединение

Рассмотрим пример запроса, который находит следующую дату изменения состояния и формирует интервал. Проанализируем этот подход шаг за шагом:


ВЫБРАТЬ
    тзГраничныеДни.Дата КАК ДатаНачала,
    ДОБАВИТЬКДАТЕ(МИНИМУМ(СледПериод.Дата), ДЕНЬ, -1) КАК ДатаОкончания,
    тзГраничныеДни.Признак
ПОМЕСТИТЬ ВТ_Интервалы
ИЗ
    ВТ_ИсходныеДанные КАК тзГраничныеДни
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК СледПериод 
    ПО тзГраничныеДни.Дата < СледПериод.Дата
    И тзГраничныеДни.Признак <> СледПериод.Признак
СГРУППИРОВАТЬ ПО
    тзГраничныеДни.Дата,
    тзГраничныеДни.Признак

В данном фрагменте мы соединяем таблицу саму с собой, чтобы для каждой записи найти минимальную дату, которая больше текущей и имеет другое значение признака. Это и будет границей нового периода. Однако, чтобы этот запрос работал корректно для нескольких сотрудников, нам нужно добавить условия по ключевым полям.

Вариант 2: Оптимизированный запрос для нескольких объектов (сотрудников)

Когда данных много и объектов несколько, структура запроса усложняется. Рассмотрим более полный пример, который учитывает сотрудников и корректно закрывает периоды:


ВЫБРАТЬ
    МИНИМУМ(Данные.ДатаНачала) КАК ДатаНачала,
    Данные.ДатаОкончания КАК ДатаОкончания,
    Данные.Сотрудник
ИЗ
    (ВЫБРАТЬ
        тзГраничныеДни.Дата КАК ДатаНачала,
        // Находим конец периода как день перед началом следующего состояния
        ЕСТЬNULL(ДОБАВИТЬКДАТЕ(МИНИМУМ(СледПериод.Дата), ДЕНЬ, -1), &КонецПериода) КАК ДатаОкончания,
        тзГраничныеДни.Сотрудник
    ИЗ
        ВТ_Данные КАК тзГраничныеДни
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Данные КАК СледПериод
        ПО тзГраничныеДни.Сотрудник = СледПериод.Сотрудник
        И тзГраничныеДни.Дата < СледПериод.Дата
        // Важное условие: ищем именно момент смены состояния
        И тзГраничныеДни.Признак <> СледПериод.Признак
    СГРУППИРОВАТЬ ПО
        тзГраничныеДни.Дата,
        тзГраничныеДни.Сотрудник) КАК Данные
СГРУППИРОВАТЬ ПО
    Данные.ДатаОкончания,
    Данные.Сотрудник
УПОРЯДОЧИТЬ ПО
    Данные.Сотрудник,
    ДатаНачала

Важные замечания по алгоритму:

Когда лучше использовать программный перебор?

Несмотря на изящность запросов, проанализируем ситуацию с точки зрения производительности. Если ваша таблица содержит десятки тысяч строк (например, данные по всем сотрудникам крупного предприятия за год), вложенные запросы с соединениями ПО Дата < СледДата могут работать медленно (квадратичная сложность).

В таких случаях эффективнее выполнить один простой запрос с упорядочиванием по Сотрудник, Дата и обойти результат в цикле на встроенном языке 1С:


ТекущийСотрудник = Неопределено;
ТекущееЗначение = Неопределено;
ДатаНачала = Дата(1,1,1);

Для Каждого Строка Из ТаблицаДанных Цикл
    Если Строка.Сотрудник <> ТекущийСотрудник Или Строка.Значение <> ТекущееЗначение Тогда
        // Записываем накопленный период, если это не первая итерация
        Если ТекущийСотрудник <> Неопределено Тогда
            НоваяСтрока = Результат.Добавить();
            НоваяСтрока.Сотрудник = ТекущийСотрудник;
            НоваяСтрока.Начало = ДатаНачала;
            НоваяСтрока.Конец = КонецПредыдущегоДня;
            НоваяСтрока.Значение = ТекущееЗначение;
        КонецЕсли;
        
        // Сбрасываем счетчики для нового интервала
        ТекущийСотрудник = Строка.Сотрудник;
        ТекущееЗначение = Строка.Значение;
        ДатаНачала = Строка.Дата;
    КонецЕсли;
    КонецПредыдущегоДня = Строка.Дата;
КонецЦикла;

Этот метод гарантирует линейную скорость обработки данных и часто оказывается предпочтительнее в сложных кадровых отчетах. Выбор между запросом и перебором зависит от объема данных и конкретных требований к архитектуре вашего решения.

← На главную