An operator needs to keep part of a session's capacity under a name: places held for school groups, guests, partners or the technical team. Those places must leave the public count straight away, without anybody booking them, and only a named audience may take them back. Ticketing systems call this a contingent.
Nothing in the module covers it. quota on a resource tariff is one tariff's allocation, so two tariffs cannot share one and a quota nobody uses withholds nothing from the room. A pooled area is a capacity slice a booking names, but it is geographic. booking_channels decides who sees a tariff, not which capacity a booking draws from.
In core, not a submodule
Capacity is what BookingManager owns, so this belongs beside it rather than in a submodule reaching through the availability seam, which exists for what core cannot see. That also removes work: no bound provider, no constraint plugin, no install hook adding a field to another module's entity.
yoyaku_allotment, tenant level, is the name itself and carries no number. How many places are held changes every season; the name outlives it and is what a ticket and a report keep meaning.yoyaku_resource_allotmentsays how many places every session of a resource holds, and when it lets go.yoyaku_slot_allotmentsays how one session departs from that, each side of it answered independently.
An allotment holds places and never limits anyone
A line drawing on one is bounded by its own quota and by the room, exactly like any other line. The only difference is which places it may reach: its own allotment's, but not the ones other allotments hold. A line drawing on no allotment is bounded by the room minus everything the allotments hold.
So the pile is spent before the house is touched, and that falls out of the arithmetic rather than any assignment step: each booking reduces what its allotment withholds, and only when that reaches zero does the general count move. Twenty held and twenty booked costs everybody else nothing; the twenty-first is the first booking anyone else pays for, and it is not refused.
Capping is quota's job, per tariff. An allotment deliberately does not cap, because two fields answering "at most how many of this tariff" would mean knowing which one bites first. The cost, stated rather than buried: a cap shared across several tariffs cannot be expressed.
Nothing is booked to achieve any of this. Placeholder bookings would reach the same number, poison every transaction report, and turn a deletion into a migration.
Where a line's allotment comes from
A tariff names the allotment it draws on, on all three tariff scopes including the preset cell. The booking is then stamped with the allotment it was booked under, and never derives it again.
The stamp says whose booking this is, not which places it physically came out of, so a line made after the pile is spent still carries it. That matters beyond wording: where an allotment owns named places, the stamp is what lets a line sit in them, and a school arriving after the count runs out must still reach a reserved place standing empty. Entitlement is not accounting.
Release, and saying "nothing tonight"
release_at on the session, release_before as an offset on the resource. Once released the allotment withholds nothing, and its tariff carries on against the house with no rule of its own: an allotment that has let go behaves exactly like one whose pile is spent.
The number on a session's record says three things, all honest values of a number: empty inherits what the resource holds, 0 holds nothing tonight, 20 holds twenty. So an evening whose schools cancelled sets that record to zero.
Performance
A site holding nothing under an allotment pays no database query at all once warm; whether the feature is used is answered from a cache item that is deliberately untagged, because validating a cache tag is itself a query. Where allotments exist, a whole page costs one query warm and two cold, and nothing per session. What each allotment has taken adds no query: it is grouped out of the scan the engine already makes per tariff. A hold takes one locked read where it used to take three, which shortens how long the slot row lock is held.
All of it measured by AllotmentQueryBudgetTest rather than asserted in prose.
Scope
MR1, in core: the three entities, the resolution, the reference and the stamp, the counting, the admin screens, the ticket line, the deletion guard, docs and French. MR2, in yoyaku_placement: named places pinned to an allotment, one row per place, as a subset of the declared number rather than an alternative to it, with the colour on the venue map.
Settled
- Deleting an allotment that bookings came out of is refused, on the screen and from a script.
- Allotments claiming more than the session holds is allowed, with a standing warning rather than a validation error, so a reconfigured hall does not refuse to save.
- A pinned place may not sit inside a pooled area.
- Two rules for MR2 to carry: the number and the pinned places can disagree in both directions, and a line drawing on an allotment should be offered that allotment's own places first.
Issue fork yoyaku-3615101
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
Comment #3
mably commentedMR !187 is up: the counted allotment, in core.
Three things changed from the plan in the summary, all after discussion.
It is in core, not a new submodule. An allotment is a capacity concept, and capacity is what
BookingManagerowns:capacity,quota,limitFor()andassertFits()all live there. Theyoyaku.availability_boundseam exists for what core genuinely cannot see, a hall's physical places, whereas an allotment is a number core owns outright. Doing it in core also removed work rather than adding it: no bound provider, no constraint plugin, and no install hook putting a field into another module's entity. Only the placed half stays an extension, and it belongs inyoyaku_placementwhere the venue and the map already are.An allotment can release its unbooked places.
release_aton the session, andrelease_beforeas an offset on the resource so a season states it once. Once past, the allotment withholds nothing and its tariff falls back to the general capacity rather than refusing, so it dissolves rather than closing and a late booking still works. Nothing is written when the deadline passes, because the withheld term was always derived.Pinned places will be a subset of the size, not an alternative to it. An allotment of 20 may pin 10 named places and leave 10 floating. The place bound and the count bound already meet by
min(), so this needs no new mechanism, and it means the placed record in MR2 carries no capacity of its own.The three questions the summary left open are settled: deleting an allotment that bookings came out of is refused, on the screen and from a script; allotments claiming more than the session holds is allowed, with a standing warning on the session's screen rather than a validation error, so a reconfigured hall does not refuse to save; and a pinned place may not sit inside a pooled area.
One gap is inherited on purpose: an empty allotment on a slot tariff means inherit, so one session cannot send a tariff back to the general capacity while its resource tariff points at one.
quotahas the identical gap, and closing it later means an explicit option rather than a second reading of an empty value.Comment #4
mably commentedComment #6
mably commented