Расписание

Weekly-правила, исключения (time-off), bulk-пресеты, копирование дня, превью-сетка, сезонные графики.

Расписание

Расписание — это то, по чему slot-engine считает свободные окна для записи. Состоит из двух слоёв:

  • Weekly-правила (service_availability_rule) — рабочее время мастера/ресурса по дням недели.
  • Исключения (service_time_off) — точечные блокировки: отпуск, праздник, выходной, разовая занятость.

Оба слоя — на стороне ресурса (а не на стороне услуги): один мастер делает все привязанные к нему услуги по одному графику. Опционально можно ограничить правило конкретной услугой.

Где править

/account/services → вкладка «Расписание».

Сверху — селектор мастера (пилюли). Под ним — недельная сетка, ниже — превью-сетка на 14 дней с цветовой кодировкой свободно/занято.

Weekly-правила

Каждое правило: weekday + startTime + endTime + (опционально) effectiveFrom/effectiveTo.

ПолеОписание
weekday0 = понедельник, 6 = воскресенье (ISO 8601).
startTime / endTimeHH: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-правила на указанный промежуток.

ПолеОписание
resourceIdNULL = блокировка для всех ресурсов workspace (например, «1 января весь салон закрыт»). Иначе — конкретный ресурс.
startAt / endAtUTC-таймстампы.
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_ruleWeekly-правила
service_time_offИсключения

На этой странице