В рамках разработки собственного решения для хостела на платформе 1С, мы сталкиваемся с фундаментальной задачей учета занятости комнат и мест. Изначальная попытка использовать регистр накопления вида "Остатки" часто приводит к ситуации, когда система постоянно сигнализирует о занятости, что вызывает недоумение. Давайте подробно разберем, почему так происходит и какие методы позволяют корректно организовать учет с учетом интервального бронирования.
Основная причина, по которой регистр накопления вида Остатки может давать неверную информацию о занятости, заключается в отсутствии движений Расход. Регистр Остатки аккумулирует информацию о наличии чего-либо на определенный момент времени.
Если мы создаем только движения Приход при заселении или бронировании, но не формируем движения Расход при выселении или отмене брони, то для системы комната или место будут считаться постоянно занятыми. Каждый Приход добавляет "единицу занятости", но без соответствующего "убытия" (Расход) остаток никогда не уменьшается.
Таким образом, для корректной работы с регистром Остатки жизненно важно оформлять не только заселение, но и выселение гостей из системы, формируя соответствующие движения Приход и Расход.
Для решения задачи учета занятости комнат и мест, особенно если нам нужно найти свободное место на определенный интервал (например, на 5 ночей), существуют два основных подхода, каждый со своими особенностями.
Рассмотрим подробнее регистры, которые мы можем использовать, и их преимущества для нашей задачи.
Этот подход является наиболее гибким и эффективным для работы с интервальным бронированием. Он позволяет нам учитывать занятость по дням и легко проверять доступность на любой период.
Структура регистра:
Обороты. Этот вид регистра фиксирует изменения ресурсов за определенный период (приходы и расходы), не храня при этом итоговый остаток. Однако, с помощью виртуальных таблиц мы можем агрегировать эти данные для получения необходимой информации.Комната (ссылка на справочник комнат)Место (ссылка на справочник мест или числовой идентификатор, если мест несколько в комнате)ДатаПроживания (тип Дата, точность до дня). Это ключевое измерение, позволяющее нам "размазывать" занятость по дням.Занятость (числовой тип, например, Число с длиной 1). Мы будем использовать его для обозначения занятости места на конкретную дату.Принципы работы с регистром:
Приход для каждой даты этого периода.
// Пример записи движений Приход для бронирования
// Допустим, у нас есть функция, которая возвращает итератор по датам
// Для упрощения примера, мы предположим, что ДатаЗаезда и ДатаВыезда - это тип Дата
// ДатаВыезда - это дата, когда гость освобождает номер (утро)
// Соответственно, последняя ночь - это ночь перед ДатаВыезда
Для ТекущаяДата = ДатаЗаезда ПО ДатаВыезда - 1 Цикл
Движение = Движения.УчетЗанятостиХостела.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
Движение.Период = ТекущаяДата;
Движение.Комната = МояКомната;
Движение.Место = МоеМесто;
Движение.Занятость = 1; // 1 означает, что место занято
КонецЦикла;
Движения.УчетЗанятостиХостела.Записать();
ДатаВыезда - 1 используется для учета количества ночей. Если гость выезжает 5-го числа, последняя ночь его пребывания — это ночь с 4-го на 5-е.
Расход для каждой даты, на которую ранее был оформлен Приход.
// Пример записи движений Расход при выселении/отмене
Для ТекущаяДата = ДатаЗаезда ПО ДатаВыезда - 1 Цикл
Движение = Движения.УчетЗанятостиХостела.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = ТекущаяДата;
Движение.Комната = МояКомната;
Движение.Место = МоеМесто;
Движение.Занятость = 1; // Уменьшаем занятость
КонецЦикла;
Движения.УчетЗанятостиХостела.Записать();
Как искать свободные места на интервал?
Для поиска свободных мест на заданный интервал (например, с 10 по 15 мая), мы можем использовать запрос к виртуальной таблице регистра накопления. Мы должны проверить, что для каждой комнаты/места и для каждой даты в заданном интервале сумма приходов и расходов по ресурсу Занятость равна 0 (или не превышает вместимости, если ресурс — количество мест). Если мы используем ресурс Занятость как число (0 или 1), то для свободного места на конкретную дату остаток по этому ресурсу должен быть 0.
Рассмотрим пример запроса для поиска занятых мест, чтобы затем исключить их из общего списка и получить свободные:
// Предполагаем, что у нас есть ссылки на справочники Комнаты и Места
// и регистр накопления "УчетЗанятостиХостела" с измерениями Комната, Место, Период
// и ресурсом Занятость.
// 1. Получаем список всех существующих комнат и мест
ЗапросВсеМеста = Новый Запрос;
ЗапросВсеМеста.Текст =
"ВЫБРАТЬ
| Справочник.Комнаты.Ссылка КАК Комната,
| Справочник.Места.Ссылка КАК Место
|ИЗ
| Справочник.Комнаты КАК Комнаты
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Места КАК Места
| ПО Места.Комната = Комнаты.Ссылка
|ГДЕ
| Места.ПометкаУдаления = ЛОЖЬ
| И Комнаты.ПометкаУдаления = ЛОЖЬ"; // Добавляем условие для Комнат
ТаблицаВсехМест = ЗапросВсеМеста.Выполнить().Выгрузить();
// Теперь у нас есть список всех возможных пар Комната-Место
// 2. Определяем начало и конец интервала для поиска
НачалоИнтервала = '20231010'; // Например, 10 октября
КонецИнтервала = '20231015'; // Например, 15 октября
// 3. Получаем список всех занятых мест на каждую дату в заданном интервале
ЗапросЗанятостьПоДатам = Новый Запрос;
ЗапросЗанятостьПоДатам.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| УчетЗанятостиХостелаОбороты.Комната,
| УчетЗанятостиХостелаОбороты.Место,
| УчетЗанятостиХостелаОбороты.Период КАК ДатаЗанятости
|ИЗ
| РегистрНакопления.УчетЗанятостиХостела.Обороты(НАЧАЛОПЕРИОДА(&ДатаНачала, ДЕНЬ), КОНЕЦПЕРИОДА(&ДатаОкончания, ДЕНЬ), День, Комната, Место) КАК УчетЗанятостиХостелаОбороты
|ГДЕ
| (УчетЗанятостиХостелаОбороты.ЗанятостьПриход - УчетЗанятостиХостелаОбороты.ЗанятостьРасход) > 0"; // Место занято, если баланс > 0
ЗапросЗанятостьПоДатам.УстановитьПараметр("ДатаНачала", НачалоИнтервала);
ЗапросЗанятостьПоДатам.УстановитьПараметр("ДатаОкончания", КонецИнтервала);
ТаблицаЗанятыхМестПоДатам = ЗапросЗанятостьПоДатам.Выполнить().Выгрузить();
// Эта таблица содержит пары Комната-Место и конкретную дату, когда это место было занято
// в пределах нашего интервала.
// 4. Теперь мы можем найти места, которые полностью свободны на весь интервал.
// Для этого нам нужно найти такие пары Комната-Место, которые НЕ встречаются
// в ТаблицеЗанятыхМестПоДатам НИ НА ОДНУ дату из интервала.
// Используем временные таблицы для удобства.
МенеджерВТ = Новый МенеджерВременныхТаблиц;
ЗапросИтоговый = Новый Запрос;
ЗапросИтоговый.МенеджерВременныхТаблиц = МенеджерВТ;
// Передаем таблицу всех мест во временную таблицу
ЗапросИтоговый.Текст =
"ПОМЕСТИТЬ ВТВсеМеста
|ИЗ
| &ТаблицаВсехМест КАК ТаблицаВсехМест
|;
|
|ПОМЕСТИТЬ ВТЗанятыеМестаПоДатам
|ИЗ
| &ТаблицаЗанятыхМестПоДатам КАК ТаблицаЗанятыхМестаПоДатам
|;
|
|ВЫБРАТЬ
| ВТВсеМеста.Комната,
| ВТВсеМеста.Место
|ИЗ
| ВТВсеМеста КАК ВТВсеМеста
|ГДЕ
| НЕ ВТВсеМеста.Комната В
| (ВЫБРАТЬ РАЗЛИЧНЫЕ
| ВТЗанятыеМестаПоДатам.Комната
| ИЗ
| ВТЗанятыеМестаПоДатам КАК ВТЗанятыеМестаПоДатам)
| И НЕ ВТВсеМеста.Место В
| (ВЫБРАТЬ РАЗЛИЧНЫЕ
| ВТЗанятыеМестаПоДатам.Место
| ИЗ
| ВТЗанятыеМестаПоДатам КАК ВТЗанятыеМестаПоДатам)";
// Этот запрос ищет места, которые НЕ встречаются в списке занятых мест ВООБЩЕ,
// но нам нужно найти места, которые НЕ заняты на *весь* интервал.
// Более корректный запрос для поиска полностью свободных мест на весь интервал:
ЗапросИтоговый = Новый Запрос;
ЗапросИтоговый.МенеджерВременныхТаблиц = МенеджерВТ;
ЗапросИтоговый.Текст =
"ПОМЕСТИТЬ ВТВсеМеста
|ИЗ
| &ТаблицаВсехМест КАК ТаблицаВсехМест
|;
|
|ПОМЕСТИТЬ ВТЗанятыеМестаПоДатам
|ИЗ
| &ТаблицаЗанятыхМестПоДатам КАК ТаблицаЗанятыхМестаПоДатам
|;
|
|ВЫБРАТЬ
| ВсеМеста.Комната,
| ВсеМеста.Место
|ИЗ
| ВТВсеМеста КАК ВсеМеста
|ГДЕ
| НЕ СУЩЕСТВУЕТ (ВЫБРАТЬ
| 1
| ИЗ
| ВТЗанятыеМестаПоДатам КАК ЗанятыеМеста
| ГДЕ
| ЗанятыеМеста.Комната = ВсеМеста.Комната
| И ЗанятыеМеста.Место = ВсеМеста.Место)"; // Проверяем, существует ли хоть одна запись о занятости для данного места в интервале
ЗапросИтоговый.УстановитьПараметр("ТаблицаВсехМест", ТаблицаВсехМест);
ЗапросИтоговый.УстановитьПараметр("ТаблицаЗанятыхМестПоДатам", ТаблицаЗанятыхМестПоДатам);
СвободныеМеста = ЗапросИтоговый.Выполнить().Выгрузить();
// В таблице СвободныеМеста будут содержаться все Комнаты и Места,
// которые полностью свободны на заданный интервал от НачалоИнтервала до КонецИнтервала.
Альтернативным вариантом может быть использование периодического регистра сведений. Он также позволяет хранить информацию о занятости на каждую дату.
Структура регистра:
РегистрСведений, периодичность ПоДню.КомнатаМестоЗанято (Булево).Принципы работы: При заселении устанавливаем Занято = Истина для соответствующей записи на дату заселения, при выселении — Занято = Ложь на дату выселения. Движения в регистре сведений обычно записываются так:
// Пример записи в периодический регистр сведений при заселении
НаборЗаписей = РегистрыСведений.ЗанятостьМест.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Период.Установить(ДатаЗаезда);
НаборЗаписей.Отбор.Комната.Установить(МояКомната);
НаборЗаписей.Отбор.Место.Установить(МоеМесто);
НаборЗаписей.Прочитать();
Запись = НаборЗаписей.Добавить();
Запись.Период = ДатаЗаезда;
Запись.Комната = МояКомната;
Запись.Место = МоеМесто;
Запись.Занято = Истина;
НаборЗаписей.Записать();
И аналогично при выселении, устанавливая Занято = Ложь.
Недостатки для интервального бронирования: Главный недостаток этого подхода проявляется при поиске свободных мест на интервал. Чтобы определить, свободна ли комната на 5 ночей, нам придется итеративно проверять каждую из этих 5 дат. Это может быть менее производительно, чем запрос к регистру накопления, использующему агрегацию. Регистр сведений лучше подходит для получения состояния на конкретную дату, а не для анализа периода.
Для поиска свободных мест на интервал с использованием периодического регистра сведений, нам придется выполнить запрос к его физической таблице или использовать обход каждой даты в цикле, что будет медленнее и сложнее в реализации запросов, чем с оборотным регистром.
Чтобы наша система учета была надежной и функциональной, рассмотрим несколько важных моментов:
Разделение учета бронирования и фактической занятости: В сложных системах часто имеет смысл разделить эти два аспекта.
Визуализация данных: Для удобства администратора хостела крайне полезными будут отчеты, которые визуализируют занятость. Например:
Учет отмен и изменений: Важно предусмотреть механизмы для корректной обработки отмены бронирования (формирование Расход движений) и изменения дат заезда/выезда (корректировка существующих движений или создание новых).
Использование стандартных механизмов 1С: Помните, что платформа 1С предоставляет мощные инструменты для работы с данными. Виртуальные таблицы регистров накопления, язык запросов 1С, временные таблицы — все это позволяет строить эффективные решения.
Пример из типовых решений: В типовых конфигурациях, таких как "1С:Отель" или подсистема "Общежитие" в "1С:Колледж ПРОФ", эти принципы уже реализованы. Мы можем изучить их архитектуру для получения идей.
Таким образом, для создания гибкой и функциональной системы учета занятости комнат и мест в хостеле на платформе 1С с учетом интервального бронирования, мы рекомендуем использовать регистр накопления вида "Обороты" с измерениями "Комната/Место" и "ДатаПроживания". Этот подход, в сочетании с правильным формированием движений Приход и Расход, позволит нам эффективно управлять номерным фондом и с легкостью находить свободные места на любой заданный период.