В процессе разработки управленческих отчетов, особенно в сфере гостиничного бизнеса или управления ресурсами, часто возникает задача визуализации занятости. Исходные данные обычно хранятся в виде подневных записей в регистрах накопления или сведений. Однако для построения диаграммы Ганта или формирования компактного отчета нам необходимо преобразовать этот массив дат в непрерывные периоды «Дата начала — Дата окончания». Рассмотрим подробно, как реализовать этот алгоритм, учитывая возможные разрывы в периодах занятости.
Представим, что у нас есть таблица значений (ТЗ), содержащая информацию о занятости койко-мест. Каждая строка — это отдельный день. Если койка занята с 1 по 4 число, а затем с 6 по 10, в исходной таблице будет 9 строк. Наша цель — получить две строки: первая с периодом 01.10.19 – 04.10.19, вторая — 06.10.19 – 10.10.19.
Для решения этой задачи проанализируем последовательный алгоритм действий:
Рассмотрим наиболее гибкий способ — программный перебор ТаблицыЗначений. Этот метод позволяет легко добавлять дополнительные условия проверки, например, смену статуса проживания.
// Подготовим структуру новой таблицы
ТЗ2 = Новый ТаблицаЗначений;
ТЗ2.Колонки.Добавить("Номер");
ТЗ2.Колонки.Добавить("Комната");
ТЗ2.Колонки.Добавить("Койка");
ТЗ2.Колонки.Добавить("ДатаНачала");
ТЗ2.Колонки.Добавить("ДатаОкончания");
// Переменные для хранения состояния предыдущей строки
Номер = Неопределено;
Комната = Неопределено;
Койка = Неопределено;
ПредыдущаяДата = Неопределено;
НСТР = Неопределено;
// Сортировка обязательна для корректной группировки
ТЗ.Сортировать("Номер, Комната, Койка, День");
Для каждого Стр из ТЗ Цикл
// Проверяем, изменился ли объект или возник временной разрыв (более 1 дня)
РазрывПоДате = Ложь;
Если ПредыдущаяДата <> Неопределено Тогда
РазрывПоДате = (Стр.День - ПредыдущаяДата) > 86400;
КонецЕсли;
Если Стр.Номер <> Номер Или Стр.Комната <> Комната Или Стр.Койка <> Койка Или РазрывПоДате Тогда
// Создаем новую запись периода
Номер = Стр.Номер;
Комната = Стр.Комната;
Койка = Стр.Койка;
НСТР = ТЗ2.Добавить();
НСТР.Номер = Номер;
НСТР.Комната = Комната;
НСТР.Койка = Койка;
НСТР.ДатаНачала = Стр.День;
КонецЕсли;
// На каждой итерации обновляем дату окончания текущего периода
НСТР.ДатаОкончания = Стр.День;
ПредыдущаяДата = Стр.День;
КонецЦикла;
В данном коде мы используем свойство системы 1С, где при вычитании дат результат получается в секундах. Разница в 86400 соответствует одним суткам. Обратите внимание, что НСТР.ДатаОкончания постоянно перезаписывается до тех пор, пока не встретится разрыв. Это позволяет «растягивать» период до максимально возможного.
Если данных очень много, перебор в памяти может быть медленным. Проанализируем ситуацию, когда данные можно обработать прямо в запросе. Для этого используется техника разности дат и порядковых номеров.
Выясним причину эффективности этого метода: если мы вычтем из даты (в днях) порядковый номер строки внутри группы, то для непрерывного диапазона результат всегда будет константой. Сгруппировав данные по этой константе, мы получим начало и конец периода.
// Пример логики запроса (упрощенно)
"ВЫБРАТЬ
| втДанные.Койка,
| втДанные.День,
| РАЗНОСТЬДАТ(ДАТАВРЕМЯ(1, 1, 1), втДанные.День, ДЕНЬ) - втДанные.ПорядковыйНомер КАК ГруппаПериода
|ПОМЕСТИТЬ втСгруппированныеДанные
|ИЗ втДанные;
|
|ВЫБРАТЬ
| Койка,
| МИНИМУМ(День) КАК ДатаНачала,
| МАКСИМУМ(День) КАК ДатаОкончания
|ИЗ втСгруппированныеДанные
|СГРУППИРОВАТЬ ПО Койка, ГруппаПериода"
После того как мы получили таблицу ТЗ2 с четкими интервалами, перейдем к заполнению объекта ДиаграммаГанта. Посмотрим на основные этапы настройки этого объекта.
Важно помнить, что Диаграмма Ганта состоит из Точек (строки, например, номера и койки), Серий (категории данных) и Значений (сами интервалы времени). Рассмотрим пример заполнения с иерархией «Номер -> Комната -> Койка»:
// Очистка и базовая настройка
ДиаграммаГанта.Очистить();
ДиаграммаГанта.АвтоОпределениеПолногоИнтервала = Ложь;
ДиаграммаГанта.УстановитьПолныйИнтервал(Объект.ДатаНачала, Объект.ДатаОкончания);
СерияЗанятость = ДиаграммаГанта.УстановитьСерию("Занято");
// Предположим, у нас есть выборка по структуре номерного фонда
Пока ВыборкаНомер.Следующий() Цикл
ТочкаНомер = ДиаграммаГанта.УстановитьТочку(ВыборкаНомер.Номер);
ВыборкаКойка = ВыборкаНомер.Выбрать();
Пока ВыборкаКойка.Следующий() Цикл
ТочкаКойка = ТочкаНомер.Точки.Добавить();
ТочкаКойка.Значение = ВыборкаКойка.Койка;
ТочкаКойка.Текст = Строка(ВыборкаКойка.Койка);
// Ищем интервалы для данной койки в нашей подготовленной ТЗ2
Отбор = Новый Структура("Койка", ВыборкаКойка.Койка);
НайденныеПериоды = ТЗ2.НайтиСтроки(Отбор);
Для каждого СтрПериод Из НайденныеПериоды Цикл
// Получаем значение диаграммы для конкретной точки и серии
ТекущееЗначение = ДиаграммаГанта.ПолучитьЗначение(ТочкаКойка, СерияЗанятость);
// Добавляем интервал
Интервал = ТекущееЗначение.Добавить();
Интервал.Начало = СтрПериод.ДатаНачала;
// Добавляем 86400, чтобы закрасить весь день окончания полностью
Интервал.Конец = СтрПериод.ДатаОкончания + 86400;
Интервал.Цвет = WebЦвета.МорскаяВолна;
КонецЦикла;
КонецЦикла;
КонецЦикла;
Часто требуется визуально выделить родительские уровни (например, всю комнату), если в ней заняты все койки. Разберем, как это реализовать:
ЗначениеДиаграммыГанта есть свойство ДополнительныйЦвет, которое можно использовать для отображения фоновых состояний.Проанализируем важный нюанс с датами: так как в 1С время хранится вместе с датой, интервал, заканчивающийся 31.10.19 00:00:00, визуально не захватит этот день на диаграмме. Именно поэтому мы добавляем к дате окончания 86400 секунд, переводя границу на начало следующего дня.
Таким образом, комбинируя предварительную подготовку данных в ТаблицеЗначений и возможности иерархических точек ДиаграммыГанта, мы получаем мощный инструмент для визуализации бизнес-процессов любой сложности.