При разработке кадровых и зарплатных подсистем на платформе «1С:Предприятие 8» часто возникает задача учета временных состояний сотрудников. Один из классических примеров — совмещение должностей. Нам необходимо спроектировать структуру данных, которая позволит хранить информацию о совмещаемых сотрудником должностях на определенные периоды (интервалы времени). При этом крайне важно предусмотреть ситуацию, когда у одного сотрудника в один и тот же период может быть несколько совмещений по разным должностям.
В этой статье мы подробно разберем несколько вариантов проектирования такой структуры данных, проанализируем их сильные и слабые стороны, рассмотрим современные паттерны платформы (интервальные регистры), используемые в типовых конфигурациях вроде «1С:Зарплата и управление персоналом 3.1», а также приведем примеры запросов и алгоритмов контроля пересечения периодов.
---
Первый вариант, который часто приходит на ум разработчикам, — это использование стандартного периодического регистра сведений. Разберем его структуру.
Структура регистра:
Сотрудник (СправочникСсылка.Сотрудники)Должность (СправочникСсылка.Должности)Действует (Булево) — флаг, определяющий, начинается ли совмещение или прекращается.При таком подходе для отражения совмещения сотрудника Иванова по должности «Раб на галерах» с 01.01.2024 по 10.01.2024 в регистр необходимо записать две записи:
Для получения списка действующих совмещений на определенную дату мы можем использовать стандартную виртуальную таблицу СрезПоследних:
ВЫБРАТЬ
СрезПоследних.Сотрудник КАК Сотрудник,
СрезПоследних.Должность КАК Должность
ИЗ
РегистрСведений.СовмещенияСотрудников.СрезПоследних(&Период, ) КАК СрезПоследних
ГДЕ
СрезПоследних.Действует = ИСТИНА
Преимущества подхода:
Недостатки подхода:
СрезПоследних в сложных запросах или на больших объемах данных (сотни тысяч сотрудников) создает серьезную нагрузку на СУБД из-за вложенных подзапросов, генерируемых платформой «1С».---
Чтобы избежать проблем со СрезПоследних и сразу видеть явные интервалы времени, мы можем спроектировать непериодический регистр сведений, где даты начала и окончания периода будут храниться в явном виде.
Структура регистра:
Сотрудник (СправочникСсылка.Сотрудники)Должность (СправочникСсылка.Должности)ДатаНачала (Дата)ДатаОкончания (Дата)В данном случае открытые (бессрочные) периоды совмещения записываются с максимальной датой в поле ДатаОкончания (например, 31.12.3999 23:59:59).
Для получения активных совмещений на выбранную дату нам достаточно написать очень простой и производительный запрос к физической таблице регистра:
ВЫБРАТЬ
Регистр.Сотрудник КАК Сотрудник,
Регистр.Должность КАК Должность
ИЗ
РегистрСведений.СовмещенияСотрудников КАК Регистр
ГДЕ
&ВыбраннаяДата МЕЖДУ Регистр.ДатаНачала И Регистр.ДатаОкончания
И Регистр.Сотрудник = &Сотрудник
Проблема уникальности записей:
Если мы оставим только эти четыре измерения, платформа не позволит записать два совмещения по одному и тому же сотруднику и должности на абсолютно одинаковый период (например, если по разным приказам сотрудник совмещает одну и ту же должность дважды, или периоды пересекаются). Платформа выдаст ошибку нарушения уникальности ключа записи.
Решение проблемы уникальности:
Чтобы обойти это ограничение, необходимо добавить в измерения регистра уникальный идентификатор записи. Лучше всего для этого подходит Документ-основание (например, ссылка на документ приказа о совмещении) либо уникальный ключ в виде реквизита типа УникальныйИдентификатор, преобразованного в строку или специальное измерение.
Если регистр подчинен регистратору, то само поле Регистратор не входит в ключ уникальности для непериодического регистра, поэтому добавление измерения ДокументОснование (или ИдентификаторЗаписи) является обязательным требованием для реализации сценариев с полным пересечением периодов.
---
Если мы проанализируем современные типовые конфигурации (например, «1С:Зарплата и управление персоналом 3.1»), то увидим, что разработчики фирмы «1С» для решения задач учета периодических состояний (таких как плановые начисления, кадровые переводы, графики работы) используют продвинутый паттерн: пару регистров (Основной + Интервальный).
Рассмотрим, как спроектировать такую систему по шагам.
Мы создаем периодический регистр сведений СовмещенияСотрудников, подчиненный регистратору (документу «Совмещение должностей»). Период этого регистра соответствует дате изменения (начала или окончания совмещения).
Создается вспомогательный непериодический независимый регистр сведений СовмещенияСотрудниковИнтервальный. Его структура оптимизирована для быстрого чтения данных на любую дату или период.
Структура интервального регистра:
Сотрудник (СправочникСсылка.Сотрудники)Должность (СправочникСсылка.Должности)ДатаНачала (Дата)ДатаОкончания (Дата)Регистратор (ДокументСсылка.СовмещениеДолжностей) — для связи с документом-основанием.ДокументОснование (ДокументСсылка.СовмещениеДолжностей) — также может быть вынесен в измерения для обеспечения уникальности.Запись в интервальный регистр не должна производиться вручную в коде каждого документа. В Библиотеке стандартных подсистем (БСП) реализован специальный механизм. При записи набора записей основного регистра срабатывает подписка на событие, которая автоматически пересчитывает интервалы и транслирует их во вспомогательный интервальный регистр с помощью процедур БСП, таких как СформироватьДвиженияИнтервальногоРегистраПоИзменениям().
Почему эта схема является наиболее производительной?
При получении среза данных СУБД выполняет простейший поиск по индексу (Сотрудник, ДатаНачала, ДатаОкончания). Запрос выполняется мгновенно даже на базах данных объемом в терабайты, полностью исключая тяжелые операции соединения таблиц, характерные для виртуальной таблицы СрезПоследних.
---
При проектировании интерфейса или логики проведения документов нам обязательно потребуется контролировать корректность вводимых периодов, чтобы не допустить логических ошибок (например, назначения одного и того же совмещения дважды на один период, если это запрещено бизнес-логикой).
Для проверки пересечения двух интервалов времени [ДатаНачала1, ДатаОкончания1] и [ДатаНачала2, ДатаОкончания2] используется простое математическое правило:
Интервалы пересекаются, если: ДатаНачала1 <= ДатаОкончания2 И ДатаОкончания1 >= ДатаНачала2
Давайте реализуем эту проверку на языке запросов 1С. Допустим, мы проводим документ для сотрудника и хотим проверить, нет ли у него уже действующих совмещений по этой же должности на указанный период.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Совмещения.Регистратор КАК ДокументКонфликта,
| Совмещения.ДатаНачала КАК ДатаНачала,
| Совмещения.ДатаОкончания КАК ДатаОкончания
|ИЗ
| РегистрСведений.СовмещенияСотрудниковИнтервальный КАК Совмещения
|ГДЕ
| Совмещения.Сотрудник = &Сотрудник
| И Совмещения.Должность = &Должность
| И Совмещения.ДатаНачала <= &ДатаОкончания
| И Совмещения.ДатаОкончания >= &ДатаНачала
| И Совмещения.Регистратор <> &ТекущийДокумент";
Запрос.УстановитьПараметр("Сотрудник", ТекущийСотрудник);
Запрос.УстановитьПараметр("Должность", ТекущаяДолжность);
Запрос.УстановитьПараметр("ДатаНачала", НовыйДокумент.ДатаНачала);
// Если дата окончания не заполнена, используем "конец времен"
Запрос.УстановитьПараметр("ДатаОкончания", ?(ЗначениеЗаполнено(НовыйДокумент.ДатаОкончания), НовыйДокумент.ДатаОкончания, '39991231235959'));
Запрос.УстановитьПараметр("ТекущийДокумент", СсылкаНаПроводимыйДокумент);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
ВызватьИсключение "Обнаружено пересечение периодов совмещения! Конфликтующий документ: " + Строка(Выборка.ДокументКонфликта);
КонецЦикла;
КонецЕсли;
---
Подводя итоги, выделим ключевые рекомендации для проектирования регистра сведений «Совмещения сотрудников»:
Сотрудник, Должность, ДатаНачала, ДатаОкончания и обязательным измерением ДокументОснование для обеспечения уникальности записей при пересечении периодов.31.12.3999). Это позволит использовать эффективное условие проверки пересечений через оператор МЕЖДУ.