ef60218c4c
Wrap the overlap query and Booking.objects.create() in a single transaction.atomic() block inside BookingCreateSerializer.create(). Lock the StaffProfile row with select_for_update() so concurrent requests for the same staff slot are serialized at the DB level; only one writer can hold the lock at a time, eliminating the race window between validate() and save(). The early check in validate() is kept for fast user feedback in the common non-concurrent case. The locked re-check in create() is the correctness guarantee. On SQLite (dev/tests) FOR UPDATE is silently ignored but writes are still serialized. PostgreSQL (production) gets row-level locking. Update docs/risks.md to mark the race condition as fixed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>