Issue #3614926: A booking channel decides which resources and tariffs are on offer
Adds the booking channel: a front that bookings are made through, so a tenant can hold part of what it sells back to one of them. The case driving it is a mini-site for a special event on its own domain.
What a site sees
Nothing, until an operator creates a channel. What names no channel is offered everywhere, so an install that never touches the feature behaves exactly as before.
The seams
yoyaku_booking_channel, a config entity scoped to exactly one tenant (mini-sites are per tenant, so unlike a resource type a channel is never shared). The tenant is fixed once it exists.BookingChannelContextconsults services taggedyoyaku.booking_channel_resolverin priority order. Deliberately no default channel, unlike the tenant: a channel is a restriction, so the absence of one has to mean unrestricted. A channel belonging to another tenant than the one being served is skipped rather than honoured, and so is an id naming no channel.yoyaku_domaingains the binding a mini-site needs: a channel names the domains it is served on, on a Domains tab, refusing a domain another channel holds or that another tenant is served on.BookingChannelFieldstamps a sharedbooking_channelsfield onyoyaku_resourceandyoyaku_tariff. A session inherits its resource's.BookingChannelFilteris the one place the rule is written, and both halves ship:BookingManager::tariffsByResource()narrows the read the availability map is built from, the public slot/resource routes carry a_yoyaku_booking_channelrequirement, andRequestBookerrefuses a line naming a resource or a rate this front does not offer. Hiding is not enforcing.- The transaction records a
booking_channel, stamped once as the order is opened. A basket outlives the request that opened it, so nothing re-derives it afterwards. - A
yoyaku_booking_channelcache context, on the booking form, the calendar element, the upcoming-sessions formatter, the access check and the Views filter. yoyaku_viewsadds an "Offered on the booking channel being served" filter, two EXISTS arms rather than a join so a rate on two fronts is not listed twice.
The back office keeps seeing everything the tenant sells. That is why the rule is a route requirement on the public routes rather than part of YoyakuAccessControlHandler, where the tenant boundary lives and applies to administrators too.
Coverage
BookingChannelContextTest: the chain (first answer wins, no answer means no channel, another tenant's channel and an unknown id both defer) and the freeze on the order.BookingChannelRestrictionTest: the availability map, the refusal at the hold for a rate and for a withdrawn resource, and the route access check.DomainBookingChannelResolverTest,BookingChannelDomainsFormTest,BookingChannelDomainsTabTest: the domain binding, its validation and its tab.BookingChannelOfferedFilterTest: the Views filter, run against MySQL as well as SQLite since it emits SQL.ConfigEntityAdminTestgains the channel screens and a rate naming a channel through its own form.
Docs: a new docs/booking-channels.md, plus the module maps, the yoyaku_domain README and fr.po.
Pre-1.0, so no update hook: reinstall is the upgrade path.
The noun is booking channel, not sales channel: half of what yoyaku books is free, so a name built on selling reads wrong in the engine core. Nothing in src/ says "sale" any more.