On a placed venue, increasing a quantity with the stepper places only the difference. SlotBookingForm::reconcile() calls hold($quantity - $current, ...), and VenuePlacementProvider::place() receives that delta for a single category, while PlaceAssignment::proposeForCategory() prefers a contiguous run only within one call. So three clicks a second apart are three placements of one place each, every one of them choosing its own best place with no knowledge of where the previous went, and no notice is shown because each call placed everything it was asked for.
Before the quantity page held on click, a visitor typed 3 and pressed a button once, which was a single call for three places and came out as a run. So this arrived with #3614127: Reshape the slot booking page: a flat column of quantity spinners with price as helper text.
The second half of the same problem is older: a party buying different tariffs cannot be seated together at all. place() takes one category, and reconcile() loops over the targets one at a time, so two full-price places and one youth place are placed by three independent calls even when they are submitted together.
To reproduce: on a resource with a placed venue, click plus three times with a pause between them on one tariff, then look at the selection. The places are spread over different rows or sections. Do the same across two tariffs and the two groups are unrelated to each other.
One measurement worth recording, because it suggests the ranking deserves its own look. Two places of the same tariff, placed one immediately after the other, came out in two different rows while five free places of that very grade sat in the first of those rows, and the first of the two was not even the first free place in its own row. Whether that is the delta behaviour above, the market restriction narrowing the candidates, or the ordering not being what freeCandidates() documents, is not yet established.
Proposed direction, for discussion. Seat the whole party rather than each increment, in one of two shapes. Either an increase releases what that target already holds and re-places the new total in a single call, which gives a true run but moves places the visitor may already have been shown, and is wasteful under concurrency; or place() accepts the places already held as an anchor and extends the run from them, which never moves an existing place but cannot always succeed. The cross-tariff half wants the booker to compose one placement request covering every target in the submit, which the seam already allows, since placeHolds() takes a map of categories.
Whichever shape wins, a place the visitor has already been shown should not move without them being told, so the per-offer message added in #3614348: A stepper click made while its request is in flight is rolled back by the answer applies here too.
Issue fork yoyaku-3614361
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 #2
mably commentedA requirement for whichever shape wins: a place the visitor chose themselves has to be distinguishable from one the system assigned, so that re-placing an order moves only the automatic ones. Nothing records that today. A booking carries its place, section and subsection, and a place picked by hand on the map is stored exactly like one proposed by
PlaceAssignment, so a re-seating pass would treat them alike and would shuffle a seat somebody deliberately chose.So this wants a marker on the booking saying how its place was decided, set when the place came from the visitor rather than from the proposal, and honored by re-placement: automatic places may be moved to form a run, chosen ones are fixed points the run is built around. It also turns into a promise worth making in the interface, that choosing a seat keeps it.
Comment #4
mably commentedComment #6
mably commented