Перейти к содержанию

P2-011 Регистрации на события

Метаданные

Поле Значение
Фаза Phase 2: Core
Статус done
Приоритет critical
Связь с roadmap Roadmap - Регистрации

Контекст

Бизнес-контекст

Регистрация — запись участника на событие. Участник выбирает тип билета, заполняет форму и получает подтверждение с уникальным кодом. В Phase 2 все билеты бесплатные, поэтому регистрация подтверждается сразу.

Технический контекст

  • Registration связывает User, Event, TicketType
  • confirmation_code — уникальный 8-символьный код для check-in
  • В Phase 2: status сразу CONFIRMED (бесплатные билеты)
  • События публикуются в RabbitMQ для уведомлений

Связанные документы: - Event Service — API - Domain Model - Registration - Functional Requirements FR-6 - User Journeys - Journey 2

Цель

Реализовать полный цикл регистрации на событие с генерацией confirmation code и отменой.

Definition of Ready (DoR)

  • [x] Контекст понятен и описан
  • [x] Цель сформулирована
  • [x] Acceptance Criteria определены
  • [x] Технические детали проработаны
  • [x] Зависимости определены и разрешены
  • [x] Нет блокеров

Acceptance Criteria

Создание регистрации

  • [x] Пользователь регистрируется на событие (POST /api/v1/events/{id}/registrations)
  • [x] Выбирает тип билета
  • [x] Заполняет: first_name, last_name, email
  • [x] Поддержка custom_fields (JSONB) для дополнительных полей формы
  • [x] Генерируется уникальный confirmation_code (8 символов)
  • [x] Для бесплатных билетов статус сразу CONFIRMED
  • [x] Один пользователь — одна регистрация на событие
  • [x] sold_count в TicketType увеличивается атомарно

Валидации

  • [x] Событие в статусе PUBLISHED
  • [x] Тип билета активен и в периоде продаж
  • [x] Есть доступные билеты (available > 0)
  • [x] Для приватных событий — пользователь член группы или организации
  • [x] Email валиден

Просмотр и отмена

  • [x] Список своих регистраций (GET /api/v1/registrations/my)
  • [x] Детали регистрации (GET /api/v1/registrations/{id})
  • [x] Отмена участником (DELETE /api/v1/registrations/{id})
  • [x] Отмена организатором (с указанием причины)
  • [x] При отмене — sold_count уменьшается, место возвращается

Для организатора

  • [x] Список регистраций события (GET /api/v1/events/{id}/registrations)
  • [x] Фильтры: ticket_type, status
  • [x] Поиск по имени/email
  • [ ] Экспорт (CSV) — можно в Phase 3

События RabbitMQ

  • [x] registration.created → уведомление участнику (билет с QR)
  • [x] registration.cancelled → уведомление

Definition of Done (DoD)

  • [x] Все Acceptance Criteria выполнены (CSV экспорт — Phase 3, не блокирует DoD)
  • [x] Код написан согласно code style проекта
  • [x] Unit тесты написаны
  • [x] Integration тесты (concurrent registration, overselling prevention)
  • [x] Миграции созданы с RLS
  • [x] Code review пройден
  • [x] CI/CD pipeline проходит

Технические детали

Затрагиваемые компоненты

  • [x] Backend: event-service-api, event-service-service, event-service-db
  • [ ] Frontend: форма регистрации, мои билеты
  • [x] Database: таблица registrations с RLS
  • [ ] Infrastructure: —

Модель данных

CREATE TABLE event_service.registrations (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL,
    event_id UUID NOT NULL REFERENCES event_service.events(id),
    ticket_type_id UUID NOT NULL REFERENCES event_service.ticket_types(id),
    user_id UUID, -- NULL для анонимных регистраций
    status VARCHAR(20) NOT NULL DEFAULT 'CONFIRMED',
    confirmation_code VARCHAR(8) NOT NULL UNIQUE,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL,
    custom_fields JSONB DEFAULT '{}',
    expires_at TIMESTAMP, -- для RESERVED статуса (Phase 3)
    cancelled_at TIMESTAMP,
    cancellation_reason TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    UNIQUE(event_id, user_id) -- один пользователь — одна регистрация
);

-- RLS
ALTER TABLE event_service.registrations ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_policy ON event_service.registrations
    USING (tenant_id = current_setting('app.tenant_id', true)::uuid);

CREATE INDEX idx_registrations_event ON event_service.registrations(event_id);
CREATE INDEX idx_registrations_user ON event_service.registrations(user_id);
CREATE INDEX idx_registrations_confirmation ON event_service.registrations(confirmation_code);

API Endpoints

# For participants
POST   /api/v1/events/{id}/registrations    — регистрация
GET    /api/v1/registrations/my             — мои регистрации
GET    /api/v1/registrations/{id}           — детали
DELETE /api/v1/registrations/{id}           — отмена

# For organizers
GET    /api/v1/events/{id}/registrations    — список регистраций
DELETE /api/v1/events/{id}/registrations/{regId} — отмена организатором

Confirmation Code Generation

private String generateConfirmationCode() {
    // 8 символов: uppercase буквы + цифры (без похожих)
    String chars = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
    SecureRandom random = new SecureRandom();
    String code;
    do {
        code = random.ints(8, 0, chars.length())
            .mapToObj(chars::charAt)
            .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
            .toString();
    } while (registrationRepository.existsByConfirmationCode(code));
    return code;
}

DTO

public record CreateRegistrationRequest(
    UUID ticketTypeId,
    String firstName,
    String lastName,
    String email,
    Map<String, Object> customFields
) {}

public record RegistrationDto(
    UUID id,
    UUID eventId,
    String eventTitle,
    UUID ticketTypeId,
    String ticketTypeName,
    RegistrationStatus status,
    String confirmationCode,
    String firstName,
    String lastName,
    String email,
    Instant createdAt
) {}

Зависимости

Блокирует

  • P2-012 QR-код для билета
  • P2-014 Уведомление о регистрации

Зависит от

Out of Scope

  • Бронирование с таймером (Phase 3)
  • Платные регистрации (Phase 3)
  • Лист ожидания (Phase 3)
  • Групповые регистрации (Phase 4)
  • Custom поля формы с валидацией (Phase 4)

Заметки

  • Confirmation code должен быть читаемым (для check-in)
  • При отмене события — все регистрации отменяются автоматически
  • Рассмотреть добавление idempotency_key для предотвращения дублей
  • В Phase 3 добавятся статусы RESERVED, PENDING, EXPIRED