Как в СКД вычислить разницу между текущим приходом и предыдущим уходом в детальных записях?

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

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

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

  1. Период: Дата регистрации.
  2. Приход: Время прихода сотрудника.
  3. Уход: Время ухода сотрудника.
  4. ВремяОтсутствия: Разница между текущим временем прихода и предыдущим временем ухода (в минутах). Если текущий приход раньше предыдущего ухода, или если это первая запись, то 'ВремяОтсутствия' должно быть равно 0.

Пример желаемого результата:

Период    Приход   Уход    ВремяОтсутствия
21.01.21   8.45     10.00        0
21.01.21   10.05    13.00        5  (10.05 - 10.00)
21.01.21   14.00    18.00       60  (14.00 - 13.00)

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

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

Функция ВычислитьВыражение в СКД предназначена для агрегирования данных и обращения к значениям в контексте группировок. Если вы пытаетесь использовать ее для получения значения "предыдущей" строки в детальных записях без явных группировок или при сложной логике определения "предыдущей" записи, она может давать непредсказуемые результаты. Для корректной работы часто требуется строго определенный порядок записей и контекст группировки, чтобы СКД могла однозначно определить, что является "предыдущим" значением. В нашем случае, "предыдущий уход" должен быть самым поздним уходом, который был раньше текущего прихода в рамках одного дня для одного сотрудника, что сложно выразить через простые параметры ВычислитьВыражение.

Основной подход: расчет значения предыдущей строки на уровне запроса

Самым надежным способом получить значение из "предыдущей" записи для дальнейших вычислений является модификация запроса, который формирует данные для СКД. Наша цель – сделать так, чтобы значение "УходПредыдущий" присутствовало в той же строке результата запроса, что и "ПриходТекущий". Это позволит нам затем легко вычислить разницу между этими двумя полями.

Рассмотрим по шагам, как это можно сделать, используя механизм самосоединения (ЛЕВОЕ СОЕДИНЕНИЕ) таблиц в запросе 1С.

Шаг 1: Формирование основного запроса к регистру сведений

Предположим, наш регистр сведений называется РегистрСведений.РегистрацииВремени и имеет следующие поля:

В нашем запросе мы сначала выберем все необходимые поля из основного источника данных.


ВЫБРАТЬ
    РегистрацииВремени.Период КАК ДатаДня,
    РегистрацииВремени.Сотрудник КАК Сотрудник,
    РегистрацииВремени.ДатаПрихода КАК ДатаПриходаТекущая,
    РегистрацииВремени.ДатаУхода КАК ДатаУходаТекущая
ИЗ
    РегистрСведений.РегистрацииВремени КАК РегистрацииВремени

Шаг 2: Добавление предыдущего значения через самосоединение

Теперь нам нужно получить для каждой текущей записи время ухода, которое было непосредственно перед текущим приходом. Мы сделаем это, соединив регистр сведений "сам с собой". Для этого мы добавим в запрос вторую ссылку на тот же регистр, но с другим псевдонимом, например, ПредыдущиеРегистрации.

Условия соединения будут следующие:

  1. Соединяем по тому же Сотруднику и Периоду (дню), чтобы найти предыдущие регистрации для того же сотрудника в тот же день.
  2. Условие ПредыдущиеРегистрации.ДатаУхода < РегистрацииВремени.ДатаПриходаТекущая гарантирует, что мы рассматриваем только те уходы, которые произошли до текущего прихода.
  3. Используем агрегатную функцию МАКСИМУМ(ПредыдущиеРегистрации.ДатаУхода), чтобы из всех подходящих предыдущих уходов выбрать самый последний, который и будет нашим "УходомПредыдущим".

Обратите внимание на использование ЛЕВОЕ СОЕДИНЕНИЕ. Это важно, потому что для первой записи дня или для первой записи в целом не будет "предыдущего ухода". В этом случае МАКСИМУМ(ПредыдущиеРегистрации.ДатаУхода) вернет NULL, что корректно для нашего расчета.

Рассмотрим полный запрос:


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

Важные моменты:

  1. СГРУППИРОВАТЬ ПО: Мы группируем по всем полям из основной таблицы РегистрацииВремени, а также по Сотруднику и Периоду. Это необходимо, потому что мы используем агрегатную функцию МАКСИМУМ().
  2. УПОРЯДОЧИТЬ ПО: Крайне важно установить правильный порядок для получения корректных результатов. Упорядочивание по Период, Сотрудник и ДатаПрихода гарантирует, что данные обрабатываются в хронологическом порядке для каждого сотрудника в течение дня.

Шаг 3: Вычисление "ВремениОтсутствия" в СКД

После того как наш запрос возвращает поля ДатаПриходаТекущая и ДатаУходаПредыдущая в одной строке, мы можем легко вычислить "ВремяОтсутствия" в СКД.

  1. Откройте схему компоновки данных (СКД).
  2. Перейдите на вкладку Наборы данных и выберите ваш набор данных, основанный на запросе.
  3. Перейдите на вкладку Вычисляемые поля.
  4. Добавьте новое вычисляемое поле, например, с именем ВремяОтсутствия.
  5. В качестве выражения укажите следующую формулу:

МАКСИМУМ(0, РазностьДат(ДатаУходаПредыдущая, ДатаПриходаТекущая, Минута))

Разберем эту формулу:

Функция МАКСИМУМ(0, ...) используется для следующих целей:

Шаг 4: Настройка отчета в СКД

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

  1. Перейдите на вкладку Настройки в СКД.
  2. В выбранной вами структуре отчета (например, в таблице или списке) добавьте поле ВремяОтсутствия в выбранные поля.
  3. Убедитесь, что в выбранных полях присутствуют Период, Приход (или ДатаПриходаТекущая) и Уход (или ДатаУходаТекущая), чтобы получить требуемую структуру отчета.
  4. На вкладке Порядок крайне важно указать сортировку по ДатаДня, Сотрудник, ДатаПриходаТекущая, чтобы детальные записи выводились в хронологическом порядке, что делает логику "предыдущий" визуально понятной.

Пример использования

Давайте проанализируем, как наш подход сработает на приведенном ранее примере:

Исходные данные (упрощенно):


Сотрудник1 | 21.01.21 | 08:45:00 | 10:00:00
Сотрудник1 | 21.01.21 | 10:05:00 | 13:00:00
Сотрудник1 | 21.01.21 | 14:00:00 | 18:00:00

Результат запроса (после самосоединения):

  1. Текущая запись: 21.01.21 | 08:45:00 | 10:00:00

    Для этой записи нет ПредыдущиеРегистрации.ДатаУхода < 08:45:00. Следовательно, ДатаУходаПредыдущая будет NULL.

    Расчет: МАКСИМУМ(0, РазностьДат(NULL, 08:45:00, Минута)) = МАКСИМУМ(0, NULL) = 0.

  2. Текущая запись: 21.01.21 | 10:05:00 | 13:00:00

    Запрос найдет МАКСИМУМ(ПредыдущиеРегистрации.ДатаУхода), который меньше 10:05:00, для того же сотрудника и дня. Это будет 10:00:00.

    ДатаУходаПредыдущая = 10:00:00.

    Расчет: МАКСИМУМ(0, РазностьДат(10:00:00, 10:05:00, Минута)) = МАКСИМУМ(0, 5) = 5.

  3. Текущая запись: 21.01.21 | 14:00:00 | 18:00:00

    Запрос найдет МАКСИМУМ(ПредыдущиеРегистрации.ДатаУхода), который меньше 14:00:00. Это будет 13:00:00.

    ДатаУходаПредыдущая = 13:00:00.

    Расчет: МАКСИМУМ(0, РазностьДат(13:00:00, 14:00:00, Минута)) = МАКСИМУМ(0, 60) = 60.

Как видим, этот подход позволяет точно реализовать требуемую логику, обходя сложности с обработкой "предыдущих" значений непосредственно в СКД для детальных записей.

Заключение

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

← На главную