Problem/Motivation

The slot table declares (resource, start) as a plain index (BookingSlotStorageSchema), so nothing prevents two slot rows for the same resource at the same start. Duplicate rows are not cosmetic: each carries its own capacity, so availability for that time becomes the sum of the duplicates, and the row lock taken during a hold protects one row only. The lock protects a slot, nothing protects the identity of a slot.

This is reachable today. The admin slot generator creates rows unconditionally: GenerateSlotsForm::batchCreate() calls create() and save() per spec with no check for an existing slot at that time, so a second run over an overlapping date range and the same weekdays produces a second row for every day. Since a single run is capped at a maximum span, running the form repeatedly over adjoining ranges is the expected workflow, and overlapping by a day is an easy mistake with no warning and no visible symptom until availability reads double.

It is also a prerequisite for #3613576: Add appointment scheduling with slots materialized on booking, where the slot row is created inside the hold rather than ahead of time: two visitors booking the same virtual time concurrently would each insert their own row, each lock their own, and both would succeed.

Proposed resolution

  • Declare a unique key on resource and start in BookingSlotStorageSchema in place of the current index. A unique key serves the same range scan and sort as the index it replaces, so the read performance that index exists for is preserved.
  • Make the generator additive: skip times that already have a slot for that resource instead of creating a second one, and report the number skipped next to the number created. Without this the unique key turns a harmless duplicate into a failed batch.
  • Surface a validation error rather than a storage exception when a slot is created by hand on a time that already exists, both on the slot form and in the generator.

Remaining tasks

  • Detect existing duplicates before the key can be added, and decide the reconciliation policy: a duplicate holding bookings cannot simply be deleted, so its bookings have to move to the surviving row and the capacities be reconciled.
  • The project is pre 1.0 and does not ship update hooks, so this needs a documented manual step (find duplicates, reconcile, apply the schema change) rather than an automated upgrade path.
  • Confirm the key is correct for every shipped shape. Tiers hang off the slot as separate category rows rather than beside it, and a multi day booking is one slot with one start, so one slot per resource and start should hold for all of them. Worth stating explicitly in case a future shape wants two slots at the same start.
  • Tests: two concurrent inserts for the same resource and start, only one survives; the generator run twice over the same range creates nothing the second time and says so.

API changes

None. Saving a slot that collides now fails where it previously succeeded, which is the intent.

Issue fork yoyaku-3613578

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

  • mably committed f6246e78 on 1.x
    task: #3613578 Make the slot resource and start pair unique
    
    By: mably
    
mably’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.