Проверку доступности, уменьшение остатков и создание заказа выполняйте в одной транзакции с блокировкой всех ночей проживания.
Регистрация бронирования в отеле с проверкой наличия свободных номеров
Проверку доступности, уменьшение остатков и создание заказа выполняйте в одной транзакции с блокировкой всех ночей проживания.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Реализуйте функцию create_order(user_id: int, hotel_slug: str, start_date: date, end_date: date), которая сохраняет новое бронирование в базе данных. Если на выбранные даты нет свободных номеров, функция должна вернуть соответствующее сообщение.
Таблица availability (доступность номеров):
| hotel_slug | date | rooms_count |
|---|---|---|
| ibis_batumi | 2023-12-01 | 2 |
| ibis_batumi | 2023-12-02 | 1 |
| ibis_batumi | 2023-12-03 | 2 |
Таблица orders (существующие бронирования):
| user_id | hotel_slug | start_date | end_date |
|---|---|---|---|
| 1 | ibis_batumi | 2023-12-01 | 2023-12-03 |
Сценарий работы функции:
-
Клиент 1 (оформил бронь чуть раньше):
- заказ успешно создаётся;
- количество свободных номеров уменьшается на 1 для каждой даты периода.
-
Клиент 2 (пытается забронировать те же даты):
- получает сообщение, что свободных номеров нет.
def create_order(user_id: int, hotel_slug: str, start_date: date, end_date: date):
pass
Ответ
Проверку доступности, уменьшение остатков и создание заказа выполняйте в одной транзакции с блокировкой всех ночей проживания.
Считаем rooms_count текущим остатком, а период проживания — [start_date, end_date): день выезда не занимает номер. Для Django и моделей Availability, Order схема такая:
from django.db import transaction
from django.db.models import F
@transaction.atomic
def create_order(user_id, hotel_slug, start_date, end_date):
nights = (end_date - start_date).days
if nights <= 0:
raise ValueError('Некорректные даты')
rows = list(Availability.objects.select_for_update().filter(
hotel_slug=hotel_slug, date__gte=start_date,
date__lt=end_date).order_by('date'))
if len(rows) != nights or any(r.rooms_count < 1 for r in rows):
return 'Нет свободных номеров'
Availability.objects.filter(pk__in=[r.pk for r in rows]).update(
rooms_count=F('rooms_count') - 1)
return Order.objects.create(user_id=user_id, hotel_slug=hotel_slug,
start_date=start_date, end_date=end_date)
Нужны уникальность (hotel_slug, date) и запрет отрицательных остатков. Второй конкурентный запрос дождётся блокировок и увидит обновлённые значения. Уже учтённые заказы повторно из остатков не вычитают.