Как получить актуальную тарифную ставку сотрудника в УПП 1.3 с учетом кадровых перемещений?

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

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

Источником первоначального оклада сотрудника является документ ПриемНаРаботуВОрганизацию. Однако в процессе трудовой деятельности оклад и должность сотрудника могут неоднократно изменяться документами КадровоеПеремещениеОрганизаций. Наша задача — спроектировать и написать такой запрос, который будет безошибочно возвращать актуальные начисления и кадровые данные сотрудника на любую произвольную дату, учитывая все архитектурные нюансы платформы 1С:Предприятие.

В этой статье мы подробно разберем, почему простой срез последних по периодическим регистрам сведений часто возвращает некорректные («устаревшие») данные, как устроен механизм временных перемещений и как написать быстрый и оптимизированный запрос для СУБД (включая PostgreSQL).

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

Архитектура хранения кадровых данных и плановых начислений

Прежде чем переходить к написанию кода, проанализируем структуру хранения информации в системе. Сведения о кадровом состоянии сотрудников и их окладах хранятся в двух ключевых периодических регистрах сведений:

  1. РаботникиОрганизаций — хранит кадровые данные (подразделение, должность, график работы и т.д.).
  2. ПлановыеНачисленияРаботниковОрганизаций — хранит сведения о действующих начислениях (оклад, тарифная ставка, надбавки) и значениях их показателей.

Важно понимать, что регистр ПлановыеНачисленияРаботниковОрганизаций является более «подвижным». Движения по нему могут делать не только документы приема и перемещения, но и целый ряд других документов конфигурации:

Именно поэтому стандартный «срез последних» по этому регистру может преподносить сюрпризы, если не учитывать внутреннюю логику работы платформы.

Главная ловушка: Механизм временных изменений («ПериодЗавершения»)

Для минимизации объема базы данных разработчики фирмы 1С применили элегантное, но неочевидное для новичков решение. Когда оформляется временное кадровое перемещение (например, перевод сотрудника на другую должность с повышением оклада на период с 1 по 15 число месяца), система делает в регистр сведений всего одну запись вместо двух.

В этой записи заполняются особые реквизиты завершения:

Теперь рассмотрим ситуацию: мы делаем запрос к виртуальной таблице СрезПоследних на дату 16 число (когда временный перевод уже фактически завершился). Поскольку новых документов после 1 числа по сотруднику не проводилось, СУБД вернет нам единственную запись от 1 числа.

Если мы просто обратимся к полям Должность и Показатель1, мы получим временные данные (которые действовали до 15 числа), хотя на дворе уже 16 число и сотрудник вернулся к прежним обязанностям. Это классическая ошибка, из-за которой в печатных формах договоров появляются неверные тарифные ставки.

Разбор неоптимального и некорректного запроса

Давайте посмотрим на пример запроса, который часто пишут начинающие разработчики (подобный вариант обсуждался на форуме):


ВЫБРАТЬ
    ПлановыеНачисленияСрезПоследних.Показатель1 КАК Результат,
    РаботникиСрезПоследних.Должность КАК НоваяДолжность
ИЗ
    РегистрСведений.ПлановыеНачисленияРаботниковОрганизаций.СрезПоследних(&ДатаАктуальности, ) КАК ПлановыеНачисленияСрезПоследних
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РаботникиОрганизаций.СрезПоследних(&ДатаАктуальности, ) КАК РаботникиСрезПоследних
        ПО ПлановыеНачисленияСрезПоследних.Сотрудник = РаботникиСрезПоследних.Сотрудник
ГДЕ
    ПлановыеНачисленияСрезПоследних.Сотрудник = &Сотрудник

В этом решении кроются сразу три серьезные проблемы:

  1. Игнорирование полей завершения. Как мы выяснили выше, если дата актуальности больше даты завершения временного перевода, запрос вернет неактуальные (временные) данные.
  2. Низкая производительность. Фильтрация по сотруднику вынесена в секцию ГДЕ. Это критическая ошибка для высоконагруженных баз. Платформа 1С будет вынуждена построить срез последних в памяти СУБД по абсолютно всем сотрудникам организации, и только потом отсечет лишних. При работе на СУБД PostgreSQL это гарантированно приведет к критическому падению производительности и зависанию системы.
  3. Отсутствие контроля активности начисления. Оклад мог быть прекращен (например, при увольнении или переходе на другие виды расчета), но в срезе последних мы все равно увидим старую ставку в поле Показатель1. Нужно обязательно анализировать измерение Действие.

Пишем правильный и оптимизированный запрос

Чтобы избежать описанных проблем, мы должны применить конструкцию ВЫБОР КОГДА ... ТОГДА для анализа полей завершения, а также передать параметры отбора напрямую в параметры виртуальных таблиц.

Рассмотрим правильный вариант построения запроса:


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

Проанализируем ключевые изменения в оптимизированном запросе:

Использование штатных механизмов конфигурации УПП 1.3

Разработчикам прикладных решений в УПП 1.3 крайне редко приходится писать подобные сложные запросы вручную для прикладных задач. В конфигурации уже заложен мощный программный интерфейс (API), который спроектирован с учетом всех описанных тонкостей и оптимизирован по скорости работы.

Для получения последних кадровых сведений рекомендуется использовать функцию общего модуля ПроцедурыУправленияПерсоналом.ПолучитьПоследниеСведенияОРаботниках.

Посмотрим на пример её вызова в программном коде:


// Подготовим массив сотрудников (можно передать одного или список)
МассивСотрудников = Новый Массив;
МассивСотрудников.Добавить(СсылкаНаСотрудника);

// Вызываем типовой метод получения сведений
ТаблицаКадровыхДанных = ПроцедурыУправленияПерсоналом.ПолучитьПоследниеСведенияОРаботниках(
    МассивСотрудников, 
    ДатаАктуальности
);

Если ТаблицаКадровыхДанных.Количество() > 0 Тогда
    СтрокаДанных = ТаблицаКадровыхДанных[0];
    // Все поля завершения уже обработаны внутри функции!
    АктуальнаяДолжность = СтрокаДанных.Должность;
    АктуальноеПодразделение = СтрокаДанных.ПодразделениеОрганизации;
КонецЕсли;

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

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

← На главную