Как реализовать анализ пересечения периодов плана и факта для учета рабочего времени в 1С?

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

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

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

Метод 1: Математический расчет суммарного времени пересечений

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

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


Функция ПолучитьОбщееВремяДвухПериодовСекунды(Нач1, Кон1, Нач2, Кон2)    
    // Вычисляем границы пересечения: 
    // Начало — это максимум из двух начал, Конец — минимум из двух окончаний
    НачалоПересечения = Макс(Нач1, Нач2);
    КонецПересечения = Мин(Кон1, Кон2);
    
    Пересечение = КонецПересечения - НачалоПересечения;
    
    // Если разница положительная, значит периоды пересекаются
    Возврат ?(Пересечение > 0, Пересечение, 0);    
КонецФункции

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


ПлановыхСекунд = 0;
// ТаблицаПлана содержит колонки Начало и Конец (рабочее время без обеда)
Для Каждого ПериодПлана Из ТаблицаПлана Цикл
    ПлановыхСекунд = ПлановыхСекунд + (ПериодПлана.Конец - ПериодПлана.Начало);
    
    Для Каждого ПериодФакта Из ТаблицаФакта Цикл
        // Вычитаем только то время факта, которое пересекается с данным куском плана
        ПлановыхСекунд = ПлановыхСекунд - ПолучитьОбщееВремяДвухПериодовСекунды(
            ПериодПлана.Начало, ПериодПлана.Конец, 
            ПериодФакта.Начало, ПериодФакта.Конец
        );
    КонецЦикла;
КонецЦикла;

Сообщить("Итого зафиксировано отсутствия (сек): " + ПлановыхСекунд);

Этот метод прост в реализации, но имеет ограничение: он дает только общее время. Если нам нужно знать, в какие именно часы сотрудник отсутствовал (например, с 14:00 до 15:00), потребуется более сложный подход.

Метод 2: Алгоритм «выкусывания» интервалов (деление периодов)

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

Рассмотрим процедуру, которая модифицирует таблицу плановых периодов, разделяя их на части или удаляя целиком:


Процедура ВычестьПериоды(УменьшаемыеПериоды, ВычитаемыеПериоды)
    
    НулевыеУменьшаемыеПериоды = Новый Массив;
    
    Для Каждого ВычитаемыйПериод Из ВычитаемыеПериоды Цикл
        НулевыеУменьшаемыеПериоды.Очистить();
        
        // Кво увеличивается динамически, если мы дробим периоды
        Индекс = 0;
        Пока Индекс < УменьшаемыеПериоды.Количество() Цикл
            УменьшаемыйПериод = УменьшаемыеПериоды[Индекс];
            
            НачалоПересечения = Макс(ВычитаемыйПериод.Начало, УменьшаемыйПериод.Начало);
            КонецПересечения = Мин(ВычитаемыйПериод.Конец, УменьшаемыйПериод.Конец);
            
            // Если пересечения нет, переходим к следующему
            Если НачалоПересечения >= КонецПересечения Тогда 
                Индекс = Индекс + 1;
                Продолжить;
            КонецЕсли;
            
            // 1. Если факт начинается позже начала плана, создаем "головной" остаток
            Если НачалоПересечения > УменьшаемыйПериод.Начало Тогда
                НовыйПериод = УменьшаемыеПериоды.Добавить();
                НовыйПериод.Начало = УменьшаемыйПериод.Начало;
                НовыйПериод.Конец = НачалоПересечения;
            КонецЕсли;
            
            // 2. Если факт заканчивается раньше конца плана, создаем "хвостовой" остаток
            Если КонецПересечения < УменьшаемыйПериод.Конец Тогда
                НовыйПериод = УменьшаемыеПериоды.Добавить();
                НовыйПериод.Начало = КонецПересечения;
                НовыйПериод.Конец = УменьшаемыйПериод.Конец;
            КонецЕсли;
            
            // Текущий период считается "обработанным", удаляем его
            УменьшаемыеПериоды.Удалить(Индекс);
            // Индекс не увеличиваем, так как на место удаленного встал следующий элемент
        КонецЦикла;
    КонецЦикла;
КонецПроцедуры

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

Метод 3: Использование запросов для поиска «дырок» в графике

Для оптимизации производительности (особенно если сотрудников тысячи) задачу можно переложить на плечи SQL-сервера. Проанализируем логику запроса:

  1. Создаем временную таблицу ОтработанныеПериоды, где соединяем План и Факт по условию пересечения, обрезая факт границами плана (используя ВЫБОР КОГДА...).
  2. Затем выполняем поиск неперекрытых участков внутри плана. Это делается через левое соединение таблицы плана с таблицей отработанных периодов по условию отсутствия перекрытия.

Однако запросы в 1С плохо справляются с итерационным дроблением периодов, поэтому данный метод лучше всего подходит для простой проверки: «закрыт ли данный час плана хотя бы одним фактом».

Практические советы по работе со СКУД

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

1. Очистка данных («Грязный» факт)
Данные проходной часто содержат ошибки: двойные входы, отсутствие выхода (забыл приложить карту). Перед анализом периодов выполните предобработку:

2. Учет обеденных перерывов
Если в плане обед жестко зафиксирован (например, 12:00–13:00), мы просто исключаем этот интервал из таблицы плана перед началом сравнения. Если же обед «плавающий» (сотрудник может уйти на 1 час в любое время с 12 до 15), алгоритм усложняется: сначала нужно рассчитать суммарное время отсутствия в этот промежуток и вычесть из него разрешенный час.

3. Использование регистров расчета
Для наиболее профессиональной реализации разберем механизм Регистров расчета в 1С. Платформа 1С:Предприятие содержит встроенный движок для решения задач «период действия». Если вы запишете план в регистр расчета как основной вид расчета, а прогулы как вытесняющий, система сама вычислит ФактическийПериодДействия. Это избавит вас от написания кода по расчету пересечений вручную.

Выясним причину, почему программисты часто избегают регистров расчета: они сложны в настройке. Но для систем промышленного масштаба это самый надежный и быстрый способ получения отчетности по рабочему времени.

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

← На главную