Scheduling Rules — Правила расписания
Извлечено из кода: backend/app/models/catalog.py, backend/app/services/availability.py, backend/app/api/v1/endpoints/schedule.py, backend/app/api/v1/endpoints/public.py
SR-001: Двухуровневая модель расписания
- Описание: Расписание существует на двух уровнях: бизнес и мастер
- Условие: Любой запрос расписания или слотов
- Действие:
- Бизнес-расписание: Schedule WHERE business_id=X AND master_id IS NULL
- Мастер-расписание: Schedule WHERE business_id=X AND master_id=Y
- Приоритет: мастер > бизнес (fallback на бизнес, если у мастера нет)
- Исключения:
- Мастер может не иметь своего расписания — тогда используется бизнес-дефолт
- При создании бизнеса автоматически создаётся 7-дневное расписание (Пн-Пт: 09:00-18:00, Сб-Вс: off)
- Источник в коде:
backend/app/services/availability.py::get_day_schedule()
SR-002: Структура дня расписания
- Описание: Каждый день описывается одной записью в таблице schedules
- Условие: —
- Действие: Поля:
- day_of_week: 0=Monday .. 6=Sunday (Python weekday convention)
- start_time: "HH:MM" (строка, например "09:00")
- end_time: "HH:MM" (строка, например "18:00")
- break_start_time: "HH:MM" (nullable)
- break_end_time: "HH:MM" (nullable)
- is_active: boolean (рабочий/выходной)
- Исключения:
- Только один перерыв в день (нет поддержки множественных перерывов)
- Время хранится как строка, не как Time type
- Источник в коде:
backend/app/models/catalog.py::Schedule
SR-003: Дефолтное расписание при регистрации бизнеса
- Описание: При создании бизнеса автоматически генерируется расписание
- Условие: POST /auth/register
- Действие:
- 7 записей (0-6) с master_id=NULL
- Пн-Пт (0-4): is_active=True, 09:00-18:00, перерыв 13:00-14:00
- Сб-Вс (5-6): is_active=False, 09:00-18:00, перерыв 13:00-14:00
- Исключения:
- Все бизнесы получают одинаковый дефолт (нет учёта часового пояса)
- Дефолтное расписание мастерам НЕ создаётся при регистрации
- Источник в коде:
backend/app/api/v1/endpoints/auth.py::register()(строки 228-243)
SR-004: Дефолтное расписание мастера
- Описание: Расписание мастера создаётся по запросу через API
- Условие: POST /schedule/master/{master_id}
- Действие:
- 7 записей (0-6) с конкретным master_id
- Дефолт: Пн-Пт 09:00-18:00, Сб-Вс off
- Без перерыва (break_start_time=NULL, break_end_time=NULL)
- Если расписание уже существует — возвращает пустой массив (не дублирует)
- Исключения:
- Нет перерыва в дефолте мастера (в отличие от бизнес-расписания)
- Источник в коде:
backend/app/api/v1/endpoints/schedule.py::create_master_schedule()
SR-005: Расчёт свободных слотов (per-master)
- Описание: Для конкретного мастера на конкретную дату вычисляются busy intervals
- Условие: GET /public/{slug}/slots?date=YYYY-MM-DD&master_id=XXX
- Действие:
- Получить расписание дня (мастер или fallback на бизнес)
- Если day не active — вернуть work_start=00:00, work_end=00:00, busy=[]
- Получить все bookings мастера на эту дату (кроме cancelled/rejected)
- Собрать busy intervals: перерыв + все записи
- Merge overlapping intervals
- Вернуть: {work_start, work_end, busy_intervals}
- Исключения:
- Фронтенд сам вычисляет доступные слоты из (work_start..work_end) минус busy_intervals
- Бэкенд НЕ генерирует список конкретных слотов
- Источник в коде:
backend/app/services/availability.py::get_busy_intervals()
SR-006: Агрегированные слоты (любой мастер)
- Описание: Если мастер не выбран, слоты считаются по всем мастерам
- Условие: GET /public/{slug}/slots?date=YYYY-MM-DD (без master_id)
- Действие:
- Получить всех мастеров бизнеса
- Создать массив доступности [0..1440] (поминутно)
- Для каждого мастера: +1 за рабочие минуты, -1 за перерыв, -1 за каждую запись
- Busy = интервалы где count <= 0 (ни один мастер не свободен)
- overall_start = min(start_time всех мастеров), overall_end = max(end_time)
- Исключения:
- Если нет мастеров — fallback на бизнес-расписание (только перерыв как busy)
- O(masters * bookings) по сложности — может быть медленным при большом количестве
- Источник в коде:
backend/app/services/availability.py::get_aggregated_busy_intervals()
SR-007: Обновление расписания
- Описание: Провайдер может менять расписание через админку
- Условие: PUT /schedule/{id}
- Действие:
- Принимает как camelCase, так и snake_case поля
- Поля: startTime, endTime, isActive, breakStartTime, breakEndTime
- Авторизация: schedule.business_id == current_user.business_id
- Исключения:
- Нет валидации: start_time < end_time (можно поставить end раньше start)
- Нет валидации: break внутри рабочих часов
- Изменение расписания НЕ влияет на уже созданные записи
- Источник в коде:
backend/app/api/v1/endpoints/schedule.py::update_schedule()
SR-008: Удаление расписания мастера
- Описание: Можно удалить всё расписание мастера (все 7 дней)
- Условие: DELETE /schedule/master/{master_id}
- Действие: DELETE FROM schedules WHERE master_id=X AND business_id=Y
- Исключения:
- После удаления мастер будет использовать бизнес-расписание (fallback)
- Существующие записи не затрагиваются
- Источник в коде:
backend/app/api/v1/endpoints/schedule.py::delete_master_schedule()
SR-009: Праздники и исключения — НЕ реализованы
- Описание: Нет системы праздничных дней или одноразовых исключений из расписания
- Условие: —
- Действие: TODO: не реализовано
- Исключения: Единственный способ закрыть день — поставить is_active=False для этого дня недели (но это закроет ВСЕ такие дни)
- Источник в коде: Отсутствует
SR-010: Шаг слотов при переносе
- Описание: При переносе через Telegram бот предлагает слоты с шагом 15 минут
- Условие: Callback reschedule
- Действие:
- Бот показывает доступные часы (кнопки по 3 в ряд)
- После выбора часа — показывает минуты: 00, 15, 30, 45
- Проверяет доступность: слот должен помещаться в расписание и не пересекаться с busy minutes
- Исключения:
- Перенос возможен только на ту же дату (дата не меняется в Telegram flow)
- Через API (POST /reschedule/{booking_id}) можно перенести на другую дату
- Источник в коде:
backend/app/services/telegram_handler.py::_handle_reschedule_init()