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

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

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

Основы построения иерархии в диаграмме Ганта

Для начала разберем базовый механизм создания группировок. В 1С объект ДиаграммаГанта состоит из точек (вертикальная ось) и серий (легенда), на пересечении которых располагаются интервалы. Чтобы создать дерево, мы должны использовать коллекцию Точки. Каждая точка может содержать в себе подчиненную коллекцию Точки, что и позволяет строить неограниченную вложенность.

Рассмотрим структуру, которую необходимо реализовать: Номер -> Комната -> Койка. На каждом уровне иерархии могут быть свои интервалы занятости. Проанализируем программный метод создания такой структуры:


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

// Создание точки верхнего уровня (Номер)
ТочкаНомер = ДиаграммаГанта.УстановитьТочку(СсылкаНомер);
ТочкаНомер.Текст = СсылкаНомер.Наименование;

// Добавление подчиненной точки (Комната)
ТочкаКомната = ТочкаНомер.Точки.Добавить();
ТочкаКомната.Значение = СсылкаКомната;
ТочкаКомната.Текст = СсылкаКомната.Наименование;

// Добавление самого нижнего уровня (Койка)
ТочкаКойка = ТочкаКомната.Точки.Добавить();
ТочкаКойка.Значение = СсылкаКойка;
ТочкаКойка.Текст = СсылкаКойка.Наименование;

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

Заполнение данными и проблема производительности

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


// Пример НЕОПТИМАЛЬНОГО заполнения (запросы в цикле)
Серия = ДиаграммаГанта.УстановитьСерию("Основная");

ВыборкаНомеров = Справочники.Номера.Выбрать();
Пока ВыборкаНомеров.Следующий() Цикл
    ТочкаНомер = ДиаграммаГанта.УстановитьТочку(ВыборкаНомеров.Ссылка);
    
    // Поиск комнат для каждого номера
    ЗапросКомнаты = Новый Запрос("ВЫБРАТЬ Ссылка ИЗ Справочник.Комнаты ГДЕ Владелец = &Владелец");
    ЗапросКомнаты.УстановитьПараметр("Владелец", ВыборкаНомеров.Ссылка);
    ВыбКомнаты = ЗапросКомнаты.Выполнить().Выбрать();
    
    Пока ВыбКомнаты.Следующий() Цикл
        ТочкаКомната = ТочкаНомер.Точки.Добавить();
        ТочкаКомната.Значение = ВыбКомнаты.Ссылка;
        
        // Получение интервалов из регистра
        Значение = ДиаграммаГанта.ПолучитьЗначение(ТочкаКомната, Серия);
        Интервал = Значение.Добавить();
        Интервал.Начало = ДатаНачала;
        Интервал.Конец = ДатаКонца;
    КонецЦикла;
КонецЦикла;

Проанализируем этот подход. Если у вас 100 номеров, в каждом по 5 комнат, система выполнит более 100 дополнительных запросов к базе данных. Это приведет к значительным задержкам. Выясним, как сделать это правильно.

Оптимизация: использование одного запроса с итогами

Чтобы отчет работал быстро, мы должны извлечь все необходимые данные (структуру и периоды занятости) одним пакетом. Для этого воспользуемся механизмом ИТОГИ ПО в языке запросов 1С. Это позволит нам получить дерево данных, которое идеально ложится на структуру точек диаграммы Ганта.

Разберем шаги по оптимизации:

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

Посмотрим на пример реализации обхода такой выборки:


ТекстЗапроса = 
"ВЫБРАТЬ
|   Номера.Ссылка КАК Номер,
|   Комнаты.Ссылка КАК Комната,
|   Койки.Ссылка КАК Койка,
|   Загрузка.ДатаНачала КАК ДатаНачала,
|   Загрузка.ДатаОкончания КАК ДатаОкончания
|ИЗ
|   Справочник.Номера КАК Номера
|   ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Комнаты КАК Комнаты ПО Комнаты.Владелец = Номера.Ссылка
|   ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Койки КАК Койки ПО Койки.Владелец = Комнаты.Ссылка
|   ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Загруженность КАК Загрузка ПО Загрузка.Объект = Койки.Ссылка
|ИТОГИ ПО
|   Номер,
|   Комната,
|   Койка";

Результат = Запрос.Выполнить();
ВыборкаНомер = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

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

Такой подход позволяет сформировать отчет за один проход по данным, что критически важно для производительности системы.

Визуальное выделение уровней иерархии

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

Существует два основных способа управления цветом:

  1. Разные серии для уровней: Мы можем создать три серии: «Загрузка номера», «Загрузка комнаты» и «Загрузка койки». У каждой серии в настройках диаграммы задается свой цвет. При добавлении интервала мы просто выбираем нужную серию.
  2. Программная установка цвета интервала: Каждому объекту ДанныеИнтервалаДиаграммыГанта можно назначить свойство Цвет.

Проанализируем пример с программной установкой цвета:


// Для уровня номера - темный цвет
Интервал.Цвет = WebЦвета.СтальнойСиний;

// Для уровня комнаты - средний
Интервал.Цвет = WebЦвета.НебесноГолубой;

// Для уровня койки - светлый
Интервал.Цвет = WebЦвета.СветлоГолубой;

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

Интерактивность и дополнительные возможности

Чтобы сделать диаграмму по-настоящему полезной для бизнеса, добавим в нее элементы интерактивности:

1. Использование всплывающих подсказок. Свойство Текст у интервала позволяет выводить информацию при наведении курсора. Здесь можно указать ФИО гостя, номер брони или любые примечания, не загромождая основное поле графиков.

2. Обработка расшифровки. В свойствах поля диаграммы на форме можно настроить событие Выбор. Это позволит пользователю при двойном клике на интервал открывать документ «Бронирование» или «Проживание», на основании которого был создан этот интервал.

3. Свойство «Развернута». При формировании диаграммы с большим количеством уровней полезно по умолчанию сворачивать нижние уровни, чтобы пользователь не потерялся в данных. У объекта ДанныеТочкиДиаграммыГанта есть булево свойство Развернута, установив которое в Ложь, вы скроете вложенные элементы до момента, пока пользователь сам не решит их просмотреть.

Альтернативный вариант: Объект «Планировщик»

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

В «Планировщике» группировка реализуется через Измерения. Вы можете добавить измерение «Номер», и внутри него автоматически будут группироваться значения. Он также лучше поддерживает работу в веб-клиенте и имеет более современный интерфейс.

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

← На главную