Как в 1С преобразовать подневные записи в периоды и вывести их в диаграмму Ганта

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

В процессе разработки управленческих отчетов, особенно в сфере гостиничного бизнеса или управления ресурсами, часто возникает задача визуализации занятости. Исходные данные обычно хранятся в виде подневных записей в регистрах накопления или сведений. Однако для построения диаграммы Ганта или формирования компактного отчета нам необходимо преобразовать этот массив дат в непрерывные периоды «Дата начала — Дата окончания». Рассмотрим подробно, как реализовать этот алгоритм, учитывая возможные разрывы в периодах занятости.

Постановка задачи и логика решения

Представим, что у нас есть таблица значений (ТЗ), содержащая информацию о занятости койко-мест. Каждая строка — это отдельный день. Если койка занята с 1 по 4 число, а затем с 6 по 10, в исходной таблице будет 9 строк. Наша цель — получить две строки: первая с периодом 01.10.19 – 04.10.19, вторая — 06.10.19 – 10.10.19.

Для решения этой задачи проанализируем последовательный алгоритм действий:

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

Реализация алгоритма перебором таблицы значений

Рассмотрим наиболее гибкий способ — программный перебор ТаблицыЗначений. Этот метод позволяет легко добавлять дополнительные условия проверки, например, смену статуса проживания.


// Подготовим структуру новой таблицы
ТЗ2 = Новый ТаблицаЗначений;
ТЗ2.Колонки.Добавить("Номер");
ТЗ2.Колонки.Добавить("Комната");
ТЗ2.Колонки.Добавить("Койка");
ТЗ2.Колонки.Добавить("ДатаНачала");
ТЗ2.Колонки.Добавить("ДатаОкончания");

// Переменные для хранения состояния предыдущей строки
Номер = Неопределено;
Комната = Неопределено; 
Койка = Неопределено;
ПредыдущаяДата = Неопределено;
НСТР = Неопределено;

// Сортировка обязательна для корректной группировки
ТЗ.Сортировать("Номер, Комната, Койка, День");

Для каждого Стр из ТЗ Цикл
    
    // Проверяем, изменился ли объект или возник временной разрыв (более 1 дня)
    РазрывПоДате = Ложь;
    Если ПредыдущаяДата <> Неопределено Тогда
        РазрывПоДате = (Стр.День - ПредыдущаяДата) > 86400;
    КонецЕсли;

    Если Стр.Номер <> Номер Или Стр.Комната <> Комната Или Стр.Койка <> Койка Или РазрывПоДате Тогда
        
        // Создаем новую запись периода
        Номер = Стр.Номер;
        Комната = Стр.Комната; 
        Койка = Стр.Койка;
        
        НСТР = ТЗ2.Добавить();
        НСТР.Номер = Номер;
        НСТР.Комната = Комната;
        НСТР.Койка = Койка;
        НСТР.ДатаНачала = Стр.День;
        
    КонецЕсли;
    
    // На каждой итерации обновляем дату окончания текущего периода
    НСТР.ДатаОкончания = Стр.День;
    ПредыдущаяДата = Стр.День;
    
КонецЦикла;

В данном коде мы используем свойство системы 1С, где при вычитании дат результат получается в секундах. Разница в 86400 соответствует одним суткам. Обратите внимание, что НСТР.ДатаОкончания постоянно перезаписывается до тех пор, пока не встретится разрыв. Это позволяет «растягивать» период до максимально возможного.

Использование запроса для поиска периодов (метод Islands and Gaps)

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

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


// Пример логики запроса (упрощенно)
"ВЫБРАТЬ
|   втДанные.Койка,
|   втДанные.День,
|   РАЗНОСТЬДАТ(ДАТАВРЕМЯ(1, 1, 1), втДанные.День, ДЕНЬ) - втДанные.ПорядковыйНомер КАК ГруппаПериода
|ПОМЕСТИТЬ втСгруппированныеДанные
|ИЗ втДанные;
|
|ВЫБРАТЬ
|   Койка,
|   МИНИМУМ(День) КАК ДатаНачала,
|   МАКСИМУМ(День) КАК ДатаОкончания
|ИЗ втСгруппированныеДанные
|СГРУППИРОВАТЬ ПО Койка, ГруппаПериода"

Визуализация данных в диаграмме Ганта

После того как мы получили таблицу ТЗ2 с четкими интервалами, перейдем к заполнению объекта ДиаграммаГанта. Посмотрим на основные этапы настройки этого объекта.

Важно помнить, что Диаграмма Ганта состоит из Точек (строки, например, номера и койки), Серий (категории данных) и Значений (сами интервалы времени). Рассмотрим пример заполнения с иерархией «Номер -> Комната -> Койка»:


// Очистка и базовая настройка
ДиаграммаГанта.Очистить();
ДиаграммаГанта.АвтоОпределениеПолногоИнтервала = Ложь;
ДиаграммаГанта.УстановитьПолныйИнтервал(Объект.ДатаНачала, Объект.ДатаОкончания);

СерияЗанятость = ДиаграммаГанта.УстановитьСерию("Занято");

// Предположим, у нас есть выборка по структуре номерного фонда
Пока ВыборкаНомер.Следующий() Цикл
    ТочкаНомер = ДиаграммаГанта.УстановитьТочку(ВыборкаНомер.Номер);
    
    ВыборкаКойка = ВыборкаНомер.Выбрать();
    Пока ВыборкаКойка.Следующий() Цикл
        ТочкаКойка = ТочкаНомер.Точки.Добавить();
        ТочкаКойка.Значение = ВыборкаКойка.Койка;
        ТочкаКойка.Текст = Строка(ВыборкаКойка.Койка);
        
        // Ищем интервалы для данной койки в нашей подготовленной ТЗ2
        Отбор = Новый Структура("Койка", ВыборкаКойка.Койка);
        НайденныеПериоды = ТЗ2.НайтиСтроки(Отбор);
        
        Для каждого СтрПериод Из НайденныеПериоды Цикл
            // Получаем значение диаграммы для конкретной точки и серии
            ТекущееЗначение = ДиаграммаГанта.ПолучитьЗначение(ТочкаКойка, СерияЗанятость);
            
            // Добавляем интервал
            Интервал = ТекущееЗначение.Добавить();
            Интервал.Начало = СтрПериод.ДатаНачала;
            // Добавляем 86400, чтобы закрасить весь день окончания полностью
            Интервал.Конец = СтрПериод.ДатаОкончания + 86400; 
            Интервал.Цвет = WebЦвета.МорскаяВолна;
        КонецЦикла;
    КонецЦикла;
КонецЦикла;

Особенности раскраски родительских уровней

Часто требуется визуально выделить родительские уровни (например, всю комнату), если в ней заняты все койки. Разберем, как это реализовать:

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

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

← На главную