Состояния мест и типы удержания

Seatmap.pro отслеживает каждое место на каждом событии через двухслойную модель состояний. Первый слой – небольшой закрытый набор состояний жизненного цикла, которые движок бронирования использует для управления конкурентным доступом и доступностью. Второй слой – открытый, определяемый клиентом holdType, который несёт бизнес-контекст: почему это место удерживается, кто может его освободить и как оно должно выглядеть на схеме.

Два слоя

Слой Колонка Значения Что определяет
Жизненный цикл state закрытый enum конкурентный доступ, доступность
Тип удержания holdType открытая строка политика освобождения, оплата, отображение

Движок бронирования всегда проверяет лишь state = 'ACTIVE', чтобы определить, можно ли заблокировать место. holdType – это метаданные, управляющие бизнес-процессом вокруг этого места.

Состояния жизненного цикла

Четыре состояния, фиксированные платформой.

Состояние Значение Доступно для брони
ACTIVE Доступно для выбора Да
LOCKED Удержано, ожидает действия Нет
SOLD Подтверждено и оформлено Нет
BLOCKED Операционно недоступно Нет

Типичный поток корзины переводит место ACTIVELOCKEDSOLD. Брошенная корзина возвращает его LOCKEDACTIVE. Неисправное место остаётся в BLOCKED, пока операционная служба его не очистит.

Типы удержания

holdType – это строка, которую вы прикрепляете к месту при его переходе из ACTIVE. Она существует рядом с состоянием жизненного цикла и описывает причину удержания места.

Значения платформы по умолчанию

Два типа удержания поставляются из коробки:

Тип удержания Допустимое состояние Освобождается Оплата
CART LOCKED клиент, админ, система требуется
RESERVED LOCKED админ не требуется

CART – значение по умолчанию для эндпоинта /lock бронирования v2: отсутствие holdType в теле запроса разрешается в CART.

Примечание об автоистечении: поле ttlSeconds ниже принимается конфигурационным API и сохраняется в HoldTypeDefinition, но автоматическое освобождение блокировки по TTL пока не применяется сервисом бронирования. Удержания сохраняются до явного вызова /unlock, /sale или /revertsale. Применение TTL (запись seat_on_event.locked_until при блокировке и фоновый сборщик) запланировано в roadmap.

Определение собственных типов удержания

Каждая организация может объявлять дополнительные типы удержания через JSONB-колонку config в organization (и при желании наследовать от родительского tenant). Конфигурация читается через стандартный config API организации/арендатора, используемый для других функций, например webhooks.

Пример payload, отправляемого в POST /api/organizations/config/:

{
  "holdTypes": {
    "PARTNER_HOLD": {
      "validStates": ["LOCKED"],
      "ttlSeconds": 3600,
      "releaseApi": "ADMIN,API",
      "paymentRequired": true,
      "displayName": "Partner Hold",
      "displayColor": "#8B008B"
    },
    "COMP": {
      "validStates": ["LOCKED", "SOLD"],
      "ttlSeconds": null,
      "releaseApi": "ADMIN",
      "paymentRequired": false,
      "displayName": "Complimentary",
      "displayColor": "#FFD700"
    }
  }
}

Справочник полей:

Поле Тип Описание
validStates массив строк Для каких состояний жизненного цикла допустим этот тип удержания. Сервис бронирования отклоняет запросы с недопустимым сочетанием.
ttlSeconds целое число или null Предполагаемое окно автоосвобождения в секундах. Хранится как конфигурационные метаданные; автоосвобождение пока не применяется (см. примечание выше). null означает, что удержание сохраняется до явного освобождения.
releaseApi строка Список акторов через запятую, которым разрешено освобождать: CUSTOMER, ADMIN, SYSTEM, API.
paymentRequired boolean Должно ли за этим удержанием следовать оплата для перехода в SOLD.
skipLockedState boolean При true переход выполняется напрямую ACTIVESOLD (используется для процессов кассы).
displayName строка Человекочитаемая подпись, отображаемая в админ-интерфейсах.
displayColor строка Hex-цвет для оформления в рендерере.
displayIcon строка Необязательная ссылка на изображение или иконку для рендерера.

Наследование на уровне арендатора

Если ваша организация принадлежит арендатору, вы можете определить типы удержания на уровне арендатора один раз, и они применятся к каждой организации арендатора. Переопределения на уровне организации имеют приоритет поле за полем, поэтому организация может, например, переопределить displayColor у CART, не переписывая полное определение.

Порядок разрешения при любом поиске типа удержания:

  1. config.holdTypes[name] на уровне организации
  2. config.holdTypes[name] на уровне арендатора
  3. Значения платформы по умолчанию

Резолвер объединяет эти три слоя. Если вы переопределяете только displayColor на уровне организации, остальная часть определения наследуется от арендатора или значения платформы по умолчанию.

Использование типов удержания в booking API

Payload StateSelection бронирования v2 принимает необязательное поле holdType:

POST /api/private/v2.0/booking/lock?eventId=123e4567-e89b-12d3-a456-426614174000
Content-Type: application/json

{
  "sessionId": "abc123",
  "seats": [{ "id": 42 }],
  "holdType": "PARTNER_HOLD"
}

Сервис бронирования проверяет:

  • Тип удержания определён (как значение платформы по умолчанию, запись на уровне арендатора или на уровне организации) для вызывающей организации.
  • validStates включает целевое состояние жизненного цикла.

Неизвестные или несовпадающие типы удержания возвращают 400 Bad Request. Эндпоинты /unlock и /revertsale не принимают holdType – возврат места в ACTIVE очищает любое прикреплённое удержание.

Конкурентный доступ и условная блокировка

Каждый переход состояния – это условное SQL-обновление, которое утверждает ожидаемое текущее состояние в своём предложении WHERE. Два конкурентных запроса, блокирующих одно и то же место, не могут оба завершиться успешно – обновление второго запроса затрагивает ноль строк, и API возвращает false. Это делает двойное бронирование невозможным на уровне базы данных, независимо от таймингов потока корзины.

Журнал аудита

Каждый успешный переход состояния добавляет неизменяемую строку в seat_state_events, записывая:

  • Старое и новое состояние жизненного цикла
  • Тип удержания на момент перехода
  • Идентификатор сессии (когда доступен)
  • Затронутое место или группу мест
  • Временную метку

Этот журнал, работающий только на добавление, является источником истины для аналитики, заменяя приблизительную телеметрию, существовавшую ранее. Последующая отчётность может отвечать на вопросы вроде «какой процент удержаний CART конвертировался в SOLD за последнюю неделю», не затрагивая горячий путь бронирования.

Интеграция с рендерером

Booking Renderer стилизует места по их состоянию жизненного цикла (ACTIVE, LOCKED, SOLD) из коробки. Чтобы визуализировать пользовательские значения holdType – или любое другое различие – собственными цветами, анимациями или иконками, SDK рендерера предоставляет setSeatsState(seats, stateKey) и clearSeatsState(seats), управляемые декларативной конфигурацией theme.seatStyles. Считайте payload мест (включая их holdType) и примените соответствующее состояние рендерера к нужным ID мест. См. Пользовательские состояния мест (Renderer SDK) для полного API, справочника типов и проработанных примеров.

Связанное