При подготовке дополнительных соглашений к трудовым договорам или печати актуальных кадровых форм в конфигурации 1С:Управление производственным предприятием 1.3 (и аналогичных по архитектуре решениях, таких как 1С:ЗУП 2.5) разработчики часто сталкиваются с задачей получения актуальной тарифной ставки сотрудника.
Источником первоначального оклада сотрудника является документ ПриемНаРаботуВОрганизацию. Однако в процессе трудовой деятельности оклад и должность сотрудника могут неоднократно изменяться документами КадровоеПеремещениеОрганизаций. Наша задача — спроектировать и написать такой запрос, который будет безошибочно возвращать актуальные начисления и кадровые данные сотрудника на любую произвольную дату, учитывая все архитектурные нюансы платформы 1С:Предприятие.
В этой статье мы подробно разберем, почему простой срез последних по периодическим регистрам сведений часто возвращает некорректные («устаревшие») данные, как устроен механизм временных перемещений и как написать быстрый и оптимизированный запрос для СУБД (включая PostgreSQL).
Выясним причину возможных расхождений и пошагово построим правильное решение.
Прежде чем переходить к написанию кода, проанализируем структуру хранения информации в системе. Сведения о кадровом состоянии сотрудников и их окладах хранятся в двух ключевых периодических регистрах сведений:
РаботникиОрганизаций — хранит кадровые данные (подразделение, должность, график работы и т.д.).ПлановыеНачисленияРаботниковОрганизаций — хранит сведения о действующих начислениях (оклад, тарифная ставка, надбавки) и значениях их показателей.Важно понимать, что регистр ПлановыеНачисленияРаботниковОрганизаций является более «подвижным». Движения по нему могут делать не только документы приема и перемещения, но и целый ряд других документов конфигурации:
УвольнениеИзОрганизаций (прекращает начисления);ВводСведенийОПлановыхНачисленияхРаботниковОрганизаций;ВводПостоянногоНачисленияИлиУдержанияСотрудникамОрганизации;ОтпускПоУходуЗаРебенком и другие специализированные кадровые документы.Именно поэтому стандартный «срез последних» по этому регистру может преподносить сюрпризы, если не учитывать внутреннюю логику работы платформы.
Для минимизации объема базы данных разработчики фирмы 1С применили элегантное, но неочевидное для новичков решение. Когда оформляется временное кадровое перемещение (например, перевод сотрудника на другую должность с повышением оклада на период с 1 по 15 число месяца), система делает в регистр сведений всего одну запись вместо двух.
В этой записи заполняются особые реквизиты завершения:
Период — дата начала временных изменений (01 число).Должность / Показатель1 — временные значения (новая должность и новый оклад).ПериодЗавершения — дата, когда действие изменения закончится (15 число).ДолжностьЗавершения / Показатель1Завершения — прежние «базовые» значения, которые должны вернуться сотруднику автоматически по окончании временного перевода.Теперь рассмотрим ситуацию: мы делаем запрос к виртуальной таблице СрезПоследних на дату 16 число (когда временный перевод уже фактически завершился). Поскольку новых документов после 1 числа по сотруднику не проводилось, СУБД вернет нам единственную запись от 1 числа.
Если мы просто обратимся к полям Должность и Показатель1, мы получим временные данные (которые действовали до 15 числа), хотя на дворе уже 16 число и сотрудник вернулся к прежним обязанностям. Это классическая ошибка, из-за которой в печатных формах договоров появляются неверные тарифные ставки.
Давайте посмотрим на пример запроса, который часто пишут начинающие разработчики (подобный вариант обсуждался на форуме):
ВЫБРАТЬ
ПлановыеНачисленияСрезПоследних.Показатель1 КАК Результат,
РаботникиСрезПоследних.Должность КАК НоваяДолжность
ИЗ
РегистрСведений.ПлановыеНачисленияРаботниковОрганизаций.СрезПоследних(&ДатаАктуальности, ) КАК ПлановыеНачисленияСрезПоследних
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РаботникиОрганизаций.СрезПоследних(&ДатаАктуальности, ) КАК РаботникиСрезПоследних
ПО ПлановыеНачисленияСрезПоследних.Сотрудник = РаботникиСрезПоследних.Сотрудник
ГДЕ
ПлановыеНачисленияСрезПоследних.Сотрудник = &Сотрудник
В этом решении кроются сразу три серьезные проблемы:
ГДЕ. Это критическая ошибка для высоконагруженных баз. Платформа 1С будет вынуждена построить срез последних в памяти СУБД по абсолютно всем сотрудникам организации, и только потом отсечет лишних. При работе на СУБД PostgreSQL это гарантированно приведет к критическому падению производительности и зависанию системы.Показатель1. Нужно обязательно анализировать измерение Действие.Чтобы избежать описанных проблем, мы должны применить конструкцию ВЫБОР КОГДА ... ТОГДА для анализа полей завершения, а также передать параметры отбора напрямую в параметры виртуальных таблиц.
Рассмотрим правильный вариант построения запроса:
ВЫБРАТЬ
// Вычисляем актуальный оклад с учетом завершения временных изменений
ВЫБОР
КОГДА ПлановыеНачисления.ПериодЗавершения <> ДАТАВРЕМЯ(1, 1, 1)
И ПлановыеНачисления.ПериодЗавершения <= &ДатаАктуальности
ТОГДА ПлановыеНачисления.Показатель1Завершения
ИНАЧЕ ПлановыеНачисления.Показатель1
КОНЕЦ КАК ТарифнаяСтавка,
// Вычисляем актуальную должность
ВЫБОР
КОГДА Работники.ПериодЗавершения <> ДАТАВРЕМЯ(1, 1, 1)
И Работники.ПериодЗавершения <= &ДатаАктуальности
ТОГДА Работники.ДолжностьЗавершения
ИНАЧЕ Работники.Должность
КОНЕЦ КАК Должность,
// Вычисляем актуальное подразделение
ВЫБОР
КОГДА Работники.ПериодЗавершения <> ДАТАВРЕМЯ(1, 1, 1)
И Работники.ПериодЗавершения <= &ДатаАктуальности
ТОГДА Работники.ПодразделениеОрганизацииЗавершения
ИНАЧЕ Работники.ПодразделениеОрганизации
КОНЕЦ КАК Подразделение
ИЗ
// Передаем отбор по сотруднику ПАРАМЕТРОМ в виртуальную таблицу!
РегистрСведений.ПлановыеНачисленияРаботниковОрганизаций.СрезПоследних(
&ДатаАктуальности,
Сотрудник = &Сотрудник
И ВидРасчета = &ВидРасчетаОклад
) КАК ПлановыеНачисления
// Объединяем с кадровыми данными сотрудников
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.РаботникиОрганизаций.СрезПоследних(
&ДатаАктуальности,
Сотрудник = &Сотрудник
) КАК Работники
ПО ПлановыеНачисления.Сотрудник = Работники.Сотрудник
ГДЕ
// Дополнительно проверяем, что начисление действительно действует на дату среза
ВЫБОР
КОГДА ПлановыеНачисления.ПериодЗавершения <> ДАТАВРЕМЯ(1, 1, 1)
И ПлановыеНачисления.ПериодЗавершения <= &ДатаАктуальности
ТОГДА ПлановыеНачисления.ДействиеЗавершения <> ЗНАЧЕНИЕ(Перечисление.ВидыДействияСНачислением.Прекратить)
ИНАЧЕ ПлановыеНачисления.Действие <> ЗНАЧЕНИЕ(Перечисление.ВидыДействияСНачислением.Прекратить)
КОНЕЦ
Проанализируем ключевые изменения в оптимизированном запросе:
СрезПоследних(&ДатаАктуальности, Сотрудник = &Сотрудник) позволяют СУБД использовать индексы таблиц базы данных. Запрос выполнится мгновенно даже на базах объемом в несколько терабайт.&ВидРасчетаОклад конкретную ссылку на элемент справочника ОсновныеНачисленияОрганизаций, соответствующий тарифной ставке.ГДЕ мы добавили проверку перечисления ВидыДействияСНачислением. Если начисление было прекращено, строка просто не попадет в выборку, что убережет нас от вывода неактуальных сумм в печатной форме.Разработчикам прикладных решений в УПП 1.3 крайне редко приходится писать подобные сложные запросы вручную для прикладных задач. В конфигурации уже заложен мощный программный интерфейс (API), который спроектирован с учетом всех описанных тонкостей и оптимизирован по скорости работы.
Для получения последних кадровых сведений рекомендуется использовать функцию общего модуля ПроцедурыУправленияПерсоналом.ПолучитьПоследниеСведенияОРаботниках.
Посмотрим на пример её вызова в программном коде:
// Подготовим массив сотрудников (можно передать одного или список)
МассивСотрудников = Новый Массив;
МассивСотрудников.Добавить(СсылкаНаСотрудника);
// Вызываем типовой метод получения сведений
ТаблицаКадровыхДанных = ПроцедурыУправленияПерсоналом.ПолучитьПоследниеСведенияОРаботниках(
МассивСотрудников,
ДатаАктуальности
);
Если ТаблицаКадровыхДанных.Количество() > 0 Тогда
СтрокаДанных = ТаблицаКадровыхДанных[0];
// Все поля завершения уже обработаны внутри функции!
АктуальнаяДолжность = СтрокаДанных.Должность;
АктуальноеПодразделение = СтрокаДанных.ПодразделениеОрганизации;
КонецЕсли;
Использование этого метода гарантирует, что при обновлении конфигурации или изменении внутренней структуры регистров ваш код сохранит работоспособность, а логика получения данных останется абсолютно корректной.
Для извлечения плановых начислений вы также можете изучить код формирования печатной формы трудового договора непосредственно в модуле менеджера документа ПриемНаРаботуВОрганизацию. Там реализованы аналогичные механизмы сбора тарифных ставок, которые можно скопировать в вашу внешнюю печатную форму или обработку.