Issue #3612598: Individual vs collective booking mode: one booking per place (core + placement seam)
Phase 1 of the placement generalization: the core booking-mode split.
What it adds
- A
booking_mode(collective | individual) on the resource type (editable on the resource type form) and an override on the resource (empty inherits the type), resolved byBookingModeResolver, defaulting to collective. BookingComposer: turns a request (slot, category, quantity, resource) into hold lines, one line of N for collective, N lines of 1 for individual. Capacity is unchanged either way.- A
PlacementProviderInterfaceseam with a coreAnonymousPlacementProviderdefault (individual bookings placed anonymously). The placement submodule (#3612599) will register a provider that assigns a seat or a zone. - The calendar booking path (
SelectionBooker, used by the calendar and the webform) routes through the composer, so booking N on an individual resource creates N bookings.
Why
Each attendee of a per-attendee event (a conference pass, a concert seat) needs their own booking: one QR, its own check-in state. A conference pass is simply individual plus a capacity, no venue.
Tests
- Unit: resolver cascade; composer collective vs individual line building.
- Kernel: an individual resource books one per unit; a collective one keeps a single booking of the quantity.
- Existing calendar tests still pass (collective is the default; behavior unchanged).
Notes
- The
booking_moderesource field is a new base field: fresh installs and tests get it automatically; an existing site installs the field storage definition by hand (no update hook pre-1.0). - Remaining entry points (the API client, the admin place-booking form) can route through the composer in a follow-up; the calendar and webform path is done here.
Phase 1 of the placement generalization. Phase 2 is #3612599 (placement module: rename + seats/zones); #3612600 adds the ticketing opt-in.