Расписание
Weekly-правила, исключения (time-off), bulk-пресеты, копирование дня, превью-сетка, сезонные графики.
Расписание
Расписание — это то, по чему slot-engine считает свободные окна для записи. Состоит из двух слоёв:
- Weekly-правила (
service_availability_rule) — рабочее время мастера/ресурса по дням недели. - Исключения (
service_time_off) — точечные блокировки: отпуск, праздник, выходной, разовая занятость.
Оба слоя — на стороне ресурса (а не на стороне услуги): один мастер делает все привязанные к нему услуги по одному графику. Опционально можно ограничить правило конкретной услугой.
Где править
/account/services → вкладка «Расписание».
Сверху — селектор мастера (пилюли). Под ним — недельная сетка, ниже — превью-сетка на 14 дней с цветовой кодировкой свободно/занято.
Weekly-правила
Каждое правило: weekday + startTime + endTime + (опционально) effectiveFrom/effectiveTo.
| Поле | Описание |
|---|---|
| weekday | 0 = понедельник, 6 = воскресенье (ISO 8601). |
| startTime / endTime | HH:MM в локальной таймзоне ресурса (или услуги, если у ресурса не задана). |
| serviceId (опционально) | Если указан — правило применяется только к этой услуге. По умолчанию — ко всем услугам ресурса. |
| effectiveFrom / effectiveTo | Дата без времени (YYYY-MM-DD). Для сезонных расписаний (зимний / летний график). NULL = бессрочно. |
| isActive | Деактивация без удаления. |
Несколько интервалов в день
UI поддерживает несколько интервалов в один weekday — например, обеденный перерыв:
Пн 09:00 – 13:00
Пн 14:00 – 18:00
Каждая строка — отдельная запись в service_availability_rule.
Bulk-пресеты
В UI расписания доступны быстрые шаблоны:
- Будни 9–18 — заполнить понедельник-пятницу;
- Каждый день 10–20 — все 7 дней;
- Только будни / Только выходные — по запросу;
- Скопировать день на «будни / выходные / всю неделю».
Это закрывает 80% типичных графиков без построчного заполнения.
Исключения (time-off)
Перекрывают weekly-правила на указанный промежуток.
| Поле | Описание |
|---|---|
| resourceId | NULL = блокировка для всех ресурсов workspace (например, «1 января весь салон закрыт»). Иначе — конкретный ресурс. |
| startAt / endAt | UTC-таймстампы. |
| reason | Текст-маркер для админки: «отпуск», «болезнь», «обед», «событие». |
Что закрывается:
- Отпуск мастера (на неделю) → 7 time-off-записей на ресурса.
- Праздник всего салона → одна запись с
resourceId=NULL. - Разовая занятость (мастер ушёл на семинар на 3 часа) → одна запись с
startAt/endAtименно на эти 3 часа.
Превью-сетка
В UI расписания есть превью на 14 дней с цветовой кодировкой:
- Зелёный — свободно (есть слоты);
- Жёлтый — частично занято (есть и занятые, и свободные слоты);
- Красный — занято полностью / закрыто.
Это позволяет визуально проверить расписание перед публикацией: если выходной залит зелёным, кто-то забыл поставить time-off.
Slot-engine
Свободные слоты считает чистая функция computeAvailableSlots (server/utils/booking-slots.ts):
- Без зависимостей — pure-функция, легко тестируется.
- DST-aware через
Intl.DateTimeFormat— переходы на летнее/зимнее время не ломают расчёт. - Шаг сетки =
service.durationMin— без под-шага. Услуга 60 минут даёт слоты10:00, 11:00, 12:00, .... - Buffer-зоны — учитываются в overlap-check, но не показываются клиенту как занятое время.
- Capacity > 1 — слот свободен, пока
count(overlapping bookings) < capacity. - Lead-часы / horizon-дни — отсекают слишком близкие и слишком далёкие слоты.
Покрыт 16 тестами (tests/server/utils/booking-slots.test.ts): TZ, weekday-фильтр, capacity, buffer-перекрытие, time-off, lead/horizon, effectiveFrom/effectiveTo, разные ресурсы.
Сезонные графики
effectiveFrom / effectiveTo на правиле позволяет сделать сезонный график:
Зимний (Пн-Пт 10–17): effectiveFrom='2025-11-01' effectiveTo='2026-03-31'
Летний (Пн-Пт 9–18): effectiveFrom='2026-04-01' effectiveTo='2026-10-31'
Slot-engine берёт только правила, активные на запрашиваемой дате.
Что хранится в БД
| Таблица | Что |
|---|---|
service_availability_rule | Weekly-правила |
service_time_off | Исключения |