Skip to content

Allotments

An allotment is a name places are set aside under: the schools, the guests, a partner, the technical team. Places held under one leave the general count straight away, with no booking behind them, and only a booking drawing on that allotment can take them back.

Ticketing systems usually call this a contingent, which is the French label the module ships.

What it is not

Three things in the module look adjacent and answer different questions.

  • A quota is one tariff's allocation. Two tariffs cannot share one, and a quota nobody uses withholds nothing from the room. An allotment is shared by every tariff drawing on it, and withholds its places whether or not anybody uses them.
  • A pooled area is a capacity slice too, but a geographic one: a pool is an area of a venue. One allotment may span areas, or belong to no venue at all.
  • A booking channel decides who sees a tariff. An allotment decides which capacity a booking draws from. Two axes; folding them together loses both.

The model

Entity Scope Says
yoyaku_allotment tenant The allotment exists and is called Schools. Carries the color.
yoyaku_resource_allotment a resource How many places every slot of it holds, and when it lets go.
yoyaku_slot_allotment one slot How this slot departs from that.
yoyaku_pinning a venue A named way of holding places: the front row to the schools. Needs yoyaku_placement.
yoyaku_pinned_place a pinning One place it holds, for one allotment, at a weight.

The allotment itself carries no number. How many places are held is a decision a resource or a single slot makes, and it changes every season, while the name outlives all of them and is what a ticket and a report have to keep meaning.

It is tenant-level rather than venue-level, unlike a place grade. The same schools take places in every hall a tenant runs, one name keeps a season's reporting whole, and a tariff preset is tenant-level too, so a venue-scoped allotment could not be named from one.

A name shared with every tenant

An allotment may belong to no tenant at all, which means every tenant reads it. An authority running several halls as separate realms holds places for the same schools and the same press in all of them, and a vocabulary typed once per realm stops a season reading the same everywhere.

It is the only partitioned type whose tenant may be empty, and it takes the shape a resource type already has: empty is shared, a named tenant is private to it. The screen offers the choice only on a site running more than one tenant, and only while the allotment is being created, for the reason a booking channel's tenant is fixed: bookings stamped with the name are read in the realm that took them, and moving the name to another realm would leave every one of them naming something their operators cannot reach.

Only the name and the color are shared. How many places are held lives on the resource and the slot, which are rows of one realm like any other, so counting, release and reporting stay inside a tenant whatever the name is shared with. Every tariff select offers the realm's own names plus the shared ones, and nothing else: another realm's names would say who is expected in a hall that is none of this operator's business.

A shared name is used by every realm and owned by none, so renaming, recoloring or deleting one needs Administer allotments shared with every tenant. Holding places under it needs nothing beyond what holding places already needs. The refusal to delete an allotment that bookings still name counts those bookings in every realm, which is why an operator can be told a shared name is still in use while their own realm shows nothing using it.

Resolution

Most specific first, and the two sides of a record answer separately:

places held  = slot record's number, else the resource record's, else none
released at  = slot record's time, else the resource record's offset counted
               back from the slot start, else never

So a matinee may hold more places for the schools and keep the season's deadline, or hold the usual number and let go of it earlier. A slot that agrees with its resource holds no record at all, which is why that table is a list of exceptions. AllotmentSettings is the only home of this rule.

A slot with no start cannot count an offset back from anything, so it never inherits a deadline.

Where a booking's allotment comes from

A tariff names the allotment it draws on, on all three tariff scopes: the resource tariff, the slot's override, and the preset cell, so a stamped production arrives with its associations. Empty means the general capacity, which is what every tariff did before allotments existed.

An operator taking a booking may name a different allotment, which is the whole of "put this one on the schools". Their choice wins over the tariff's: pass allotment in the fields of the ask being held.

A surface offering that choice must check the choose booking allotment permission before it does. The engine does not, and deliberately: it also runs under cron and queue workers, where there is no user to ask about. This is the same division the rest of the engine follows, where the channel filter refuses what a front does not offer and the engine counts what it is given.

The booking is then stamped with the allotment it was booked under, and never derives it again: the tariff may be pointed somewhere else next season, and a ticket has to keep saying who it belongs to.

The stamp says whose booking this is, not which places it physically came out of. Every booking at a tariff drawing on the schools is stamped for the schools, including the ones made after the pile is spent. That is deliberate: where an allotment owns named places, the stamp is what lets a booking sit in them, and a school arriving after the count runs out must still be able to take a reserved seat that is standing empty. Entitlement is not accounting.

One rule resolves the stamp and the counting, so a booking is never counted against one allotment and recorded against another.

Counting

allotment remaining = places held − taken from it

general             = capacity
                    − everything taken
                    − Σ over allotments still holding of
                        max(0, places held − taken from it)

That last term is what actually sets places aside: an allotment nobody has taken from still withholds its places. max(0, …) keeps an allotment an operator overrode past its own number from crediting places back to everybody else.

An allotment holds places. It never limits anyone. A booking drawing on one is bounded by its own quota and by the room, exactly like any other. The only difference is which places it may reach: its own allotment's, but not the ones other allotments are holding. A booking 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 it falls out of the arithmetic rather than out of any assignment step: each booking reduces what its allotment is still holding back, and only when that reaches zero does the general count start to move. Twenty held, twenty booked, and everybody else has lost nothing; the twenty-first is the first booking anybody else pays for.

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 is that a cap shared across several tariffs cannot be expressed: two school tariffs each capped at twenty can take forty out of the house once their shared pile is gone.

Nothing is booked to achieve this. Seeding a slot with placeholder bookings would reach the same number, poison every transaction report, and turn deleting an allotment into a migration. The counts are derived, so a deletion gives the capacity back by itself.

Where it is enforced

Both halves are in BookingManager, because capacity is what it owns: BookingAvailability::getBoundedAvailability() for what a surface draws, assertFits() for what a hold refuses, under the anchor rows it already holds.

What it costs

A site holding nothing under an allotment pays no database query at all once warm: whether anything is held is answered from a cache item that survives between requests, and every slot then resolves to "holds nothing" without a read. The item is deliberately untagged, because validating a cache tag is itself a query, which is the cost it exists to avoid; the two entity writes that can change the answer clear it instead.

Where allotments do exist, a whole page costs one query warm, the union that reads both record sets, and two cold, the other being the probe. What each allotment has taken adds nothing: it is grouped out of the scan the engine already makes per tariff. The hold path adds nothing either, for the same reason, and takes a single locked read.

Measured, not asserted: twelve slots on one resource cost 3 queries with nothing held and 5 with one allotment held, all of it per page rather than per slot. AllotmentQueryBudgetTest counts them, because a claim about query counts that nothing measures is a claim that quietly stops being true.

Holding named places

Everything above is a number: twenty places, whichever twenty. A hall can hold particular places instead, so that the front row is the schools' and the box is the guests'. That needs yoyaku_placement, because it is the module that knows a hall has places at all.

A pinning is a named way of holding a venue's places, and it holds them for every allotment at once: some rows for the schools, a box for the guests, all in one pinning. A place belongs to at most one allotment in it, which a unique key on (pinning, place) enforces, so two audiences can never be promised one place.

Which pinning runs tonight

pinning = the slot's, else the resource's, else the venue's default,
          else no place is held by name

One rung lower than the placement configuration, which stops at the resource. That rung is the point: a layout cannot say that the matinee holds the front row for the schools and the evening holds it for the guests, because both slots belong to one production. A pinning can, and a pinning made for one evening is meant to be thrown away afterwards.

The two are deliberately independent. A configuration decides whether an area is offered place by place, as a pool, or not at all. A pinning decides who holds the places that are offered by name. So the same pinning is used under several layouts, and a place in an area a layout pools or closes is simply ignored rather than refused: it is not in the place market for anybody.

Named places are a subset of the number, never an alternative

The number always comes from the resource or the slot. A pinning names candidates in held order, and each slot takes as many of them as it holds:

  • thirty places named, ten held: the ten lightest are held, the other twenty are ordinary places
  • thirty named, thirty held: all thirty
  • thirty named, nothing declared anywhere: all thirty, because naming them is then the whole declaration
  • thirty named, zero held tonight: none

So one pinning serves a production holding ten and one holding thirty, and there is no VIP 10 and VIP 20 to keep in step and nothing for a tariff to be re-pointed at. Naming more places than a slot holds is ordinary rather than an error.

The hold also shrinks as the allotment is taken up, exactly as the number does: ten held with three schools booked holds seven places, whether those three sat in named places or anywhere else. That is what stops the two bounds drifting apart, so the general capacity is never charged twice for one place.

Naming the places

The plan's Places tab offers two ways in.

Pick on the map draws the hall. Choose the allotment once, then click places to pin and unpin them, or drag a rectangle to pin every free place inside it, which is how a section of fifty seats is named without fifty clicks. A place another allotment already holds is left alone by the rectangle: a section drawn across somebody else's seats is not a decision to take them. Dragging is the picking gesture here, so holding Shift and dragging moves the plan instead. Each place is drawn in the color of whichever allotment holds it, so the sections are told apart at a glance, and the order places are clicked in becomes the held order above. Nothing is written until the form is saved.

Hold places is the same job without the map: the venue's seats listed where they sit, in the order the hall reads, so a click and a shift-click take a row. Naming a section or a row and leaving the seats unpicked takes all of it. It is the only way in for a venue with no map at all.

A plan names places of the whole venue, and a configuration may close a section outright. The availability read drops the places it closed, so both screens say which configurations would: "Balcony closed does not open 8 of the 20 places held here". Nothing is changed by the warning; what it prevents is believing the schools have twenty when a production running that configuration holds twelve.

What a booker sees

Nothing. A held place reads as unavailable on the map and cannot be chosen, and the payload a public map is sent carries no allotment at all: which audience a place is being held for is back office information, and naming it would say who is expected in the front row.

An operator holding Choose booking allotment is sent the colors, and the map rings each held place in its allotment's color. That is the one place the holding is worth seeing at a glance.

What a party may take

A party is seated from one set of places, so it may sit in an allotment's places only when every one of its bookings draws on that allotment. A party mixing allotments, or mixing one with the general capacity, sits in the open house: a place held for the schools must not seat a full-price companion just because a school ticket came in the same booking.

Which allotment a booking draws on

The slot's word about the tariff, else the tariff's own, which is how the engine reads it and how quota is read. A tariff pointed at the schools on the production and at the guests on tonight's slot draws on the guests tonight, so the schools' places are held against it like against anybody else.

What it costs

Nothing at all to a venue that holds no place by name: the pinning is resolved from fields already loaded, and no query runs.

A venue that does pays one database read per page, and it is the one that cannot be avoided: what each allotment has already taken, which moves with every booking. It covers every slot on the page at once, so a month costs what one evening does.

The pinning itself is read once and then held between requests. Every visitor of one slot gets the same list and it changes only when an operator edits the pinning or the hall, so a few thousand simultaneous bookers must not each read it out of the database. It is invalidated by the pinning, its places, the venue's places and the layout, so an edit is seen at once rather than at the cache's leisure.

Resolving which allotment a booking draws on costs nothing: the slot tariff lookup is statically cached per request and core has already primed it for the whole page before a bound provider is asked anything.

PinnedPlaceQueryBudgetTest asserts all of it: that a venue holding no place by name reads nothing, that the pinning is read cold and not warm, that an edit is seen at once, and that the page stays flat.

The open place counts beside them stay free of any allotment on purpose. They are statically cached per venue and layout, and that static cache is what lets a calendar be answered in a handful of queries; taking an allotment into it would compute a variant per allotment on the page. So the base read stays allotment-free and the held places are subtracted next to it, which is the same shape the count bound's withheld term already has.

Deleting a pinning

Refused while a slot still to come runs it, whether by its own choice, its production's or its venue's default. A past slot never blocks it, which is what makes a one-evening pinning disposable: a booking carries its own place and its own allotment, so deleting the pinning cannot change a figure that has already been reported.

Declaring the numbers once

A production's records are where the numbers live, and a season running eight productions would otherwise type "Schools 20, Guests 10" eight times. A tariff preset therefore also holds allocations: the allotment, how many places it holds, and the release offset. Applying one stamps them onto the production beside the tariffs, so who draws on the schools and how many places they get arrive together.

The two halves of a preset answer different questions and are applied separately, which is deliberate: an operator carrying this season's numbers onto a new production without touching its prices selects only the allocations. See tariff presets.

Release

An allotment nobody fills is places nobody takes, and on a free run that is still an empty house. Lowering the number or deleting the slot's record always works and the places come back on the next read, with nothing to un-book, but that relies on somebody remembering before every slot.

So an allotment carries a deadline: release_at, an absolute time on the slot, and release_before, an offset on the resource so a season says "two days before" once.

The offset is typed either as a number of seconds or as an ISO 8601 duration, so "two days before" is P2D rather than 172800 worked out by hand. Both spellings reach the same stored value, because the column holds seconds and every availability read subtracts an integer from the slot start.

Once released the allotment withholds nothing, and nothing is written when the deadline passes, because the term was always derived. Its tariff carries on against the house, which needs no special rule: an allotment holding nothing behaves exactly like an allotment whose pile is spent, and neither of them ever limited anyone. A booking taken after release is still stamped, for the same reason every other one is: the stamp says whose booking it is.

Deleting an allotment

Refused while any booking still consuming names it, on the screen and from a script alike. A booking records the allotment it was booked under, and that record is what a report and a ticket read afterwards, so losing the name would make those bookings read as though they had never belonged to anybody.

The way out is the one an operator would guess: move or cancel the bookings, then delete the name. Nothing else about an allotment is locked. The places it holds live on the resource and the slot, and letting go of them there is the ordinary way to stop using one.

More held than the slot has

Allowed. The general count floors at zero and the room still bounds every booking, so nothing can be overbooked. A hard validation error would fight every capacity change made after the allotments were set, and a hall reconfigured between slots would refuse to save.

The slot's Allotments tab carries a standing warning while it is the case, on the screen rather than once on save, because a status message is gone on the next page.

Saying "nothing tonight"

The number on a slot's record says three things, and all three are honest values of a number:

  • empty inherits whatever the resource holds
  • 0 holds nothing tonight
  • 20 holds twenty tonight

So an evening whose schools canceled sets that slot's record to zero. The allotment then withholds nothing, and its tariff simply draws on the house like any other. No second field, and no reading an empty value two ways.