Как реализовать вытеснение пересекающихся периодов дат в запросе 1С?

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

В практике разработки на платформе 1С часто возникает задача корректного отображения периодов, когда одно событие должно «вытеснять» другое. Типичный пример: закрепление сотрудника за контрагентом. Если новым документом (проведенным позже) сотрудника назначают на другой объект в тот же интервал времени, старое назначение должно «уступить» место новому. При этом, если новый период находится «внутри» старого, исходный период должен разбиться на две части — до и после вытеснения. В этой статье мы подробно разберем, как реализовать такую логику в запросе.

Анализ проблемы и постановка задачи

Рассмотрим ситуацию, описанную в исходном вопросе. Имеется регистр сведений, где хранятся записи о работе сотрудника. Каждая запись имеет дату документа (приоритет), дату начала и дату окончания периода. Нам необходимо построить отчет, который выдаст актуальную картину без наслоений. Главная сложность заключается в том, что стандартное левое соединение не позволяет легко «разрезать» интервалы на части, особенно когда нужно сохранить «хвосты» — те части исходного периода, которые не были перекрыты новым документом.

Проанализируем пример данных:

  1. Документ от 01.01.20: Сотрудник1, период с 01.01 по 10.01 (Контрагент1).
  2. Документ от 02.01.20: Сотрудник1, период с 05.01 по 20.01 (Контрагент2).

В результате работы алгоритма период Контрагента1 должен сократиться до 01.01 – 05.01, а Контрагент2 должен занять период 05.01 – 20.01. Если же появится третий документ от 03.01.20 на период 10.01 – 15.01, он должен «вырезать» середину у Контрагента2, оставив ему два отрезка: 05.01 – 10.01 и 15.01 – 20.01.

Решение методом «Оси времени» (Граничные точки)

Наиболее надежный способ решить задачу в рамках одного запроса — это использование методики элементарных интервалов. Разберем этот алгоритм по шагам.

Шаг 1. Сбор всех граничных точек.

Сначала нам нужно собрать все даты начала и все даты окончания всех периодов в одну таблицу. Это будут те «зазубрины» на оси времени, по которым мы будем резать наши интервалы. Проанализируем, как это выглядит в коде запроса:


ВЫБРАТЬ
    Регистр.Сотрудник КАК Сотрудник,
    Регистр.Начало КАК ДатаТочки
ПОМЕСТИТЬ ВТ_Точки
ИЗ
    РегистрСведений.Закрепления КАК Регистр

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    Регистр.Сотрудник,
    Регистр.Конец
ИЗ
    РегистрСведений.Закрепления КАК Регистр
;

Шаг 2. Формирование элементарных интервалов.

Теперь из уникальных точек для каждого сотрудника нам нужно создать непересекающиеся отрезки. Для этого мы соединяем таблицу точек саму с собой. Условие соединения: левая точка меньше правой. Чтобы получить ближайшую правую точку, используем группировку и функцию МИНИМУМ.


ВЫБРАТЬ
    Т1.Сотрудник КАК Сотрудник,
    Т1.ДатаТочки КАК ДатаНачала,
    МИНИМУМ(Т2.ДатаТочки) КАК ДатаОкончания
ПОМЕСТИТЬ ВТ_ЭлементарныеИнтервалы
ИЗ
    ВТ_Точки КАК Т1
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Точки КАК Т2
        ПО Т1.Сотрудник = Т2.Сотрудник
            И Т1.ДатаТочки < Т2.ДатаТочки
ГРУППИРОВАТЬ ПО
    Т1.Сотрудник,
    Т1.ДатаТочки
;

Шаг 3. Наложение данных и выбор приоритета.

Теперь у нас есть «нарезка» времени. Каждый такой маленький интервал мы соединяем с исходным регистром. Если интервал попадает внутрь периода регистрации, значит, эта запись претендует на данный отрезок. Но победит только та, у которой максимальная дата документа (оперативный приоритет).


ВЫБРАТЬ
    Интервалы.Сотрудник,
    Интервалы.ДатаНачала,
    Интервалы.ДатаОкончания,
    МАКСИМУМ(Регистр.ДатаДокумента) КАК МаксДатаДокумента
ПОМЕСТИТЬ ВТ_Победители
ИЗ
    ВТ_ЭлементарныеИнтервалы КАК Интервалы
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.Закрепления КАК Регистр
        ПО Интервалы.Сотрудник = Регистр.Сотрудник
            И Интервалы.ДатаНачала >= Регистр.Начало
            И Интервалы.ДатаОкончания <= Регистр.Конец
ГРУППИРОВАТЬ ПО
    Интервалы.Сотрудник,
    Интервалы.ДатаНачала,
    Интервалы.ДатаОкончания
;

Шаг 4. Получение итоговых данных.

На финальном этапе мы соединяем таблицу «победителей» с исходным регистром по сотруднику и дате документа, чтобы вытащить ресурс (Контрагента).

Использование механизмов ЗУП 3.1

Рассмотрим альтернативный вариант. В конфигурациях, основанных на БСП и типовых решениях (ЗУП 3.1, ERP), существуют программные модули для работы с интервальными регистрами. Например, ЗарплатаКадрыПериодическиеРегистры. Эти модули умеют строить временные таблицы с вытеснением программно.

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

Важные нюансы реализации

Проанализируем технические детали, которые могут повлиять на корректность результата:

  1. Момент времени: Использовать просто Дата документа в качестве приоритета опасно. Если два документа проведены в одну секунду, их порядок в запросе станет неопределенным. Профессиональнее использовать МоментВремени или комбинацию Дата + Ссылка.
  2. Границы периодов: В 1С дата окончания периода часто включает последнюю секунду дня (23:59:59). При сравнении <= и >= важно следить, чтобы точки начала и конца не создавали «пустых» интервалов длиной в 0 секунд.
  3. Производительность: Метод «Оси времени» при большом количестве записей (сотни тысяч) может работать медленно из-за перекрестных соединений. В этом случае стоит накладывать фильтры по конкретным сотрудникам или периодам уже на первом этапе формирования ВТ_Точки.

Почему визуализация (Диаграмма Ганта) не заменяет расчет?

Многие начинающие разработчики полагают, что задачу можно решить на уровне вывода в Диаграмму Ганта, так как она сама умеет отрисовывать наслоения. Однако выясним причину, почему это не подходит: отчетность часто требует вывода итоговых цифр (количество дней, сумма за период). Диаграмма Ганта — это лишь объект формы, она не возвращает таблицу данных. Для корректного расчета премий или стоимости услуг нам необходима именно «плоская» таблица без пересечений, полученная в результате вышеописанного запроса.

Таким образом, мы рассмотрели универсальный алгоритм вытеснения, который позволяет обрабатывать любые пересечения и сохранять остаточные периоды («хвосты»). Этот подход является стандартом де-факто для сложных кадровых отчетов и систем планирования в 1С.

← На главную