Issue #3613507: Sell a pooled area from the picker: one block with a quantity, beside the seats

MR2 of two on this issue, on top of the merged !107 (merged). The model, the booking path and availability learned about pooled areas there; the picker did not, so a pooled area vanished from the map (its places are deliberately not on sale as places) and a venue selling only pools reported that nothing was available at all. Pooled events were bookable through the calendar and nowhere else.

The payload

VenueMapBuilder now carries every pooled area as a block: its name (section and slice, worded as the configuration form words it), its capacity, what is left, how many units the visitor already holds and with which tariff, and the tariffs that may sell it.

A tariff may sell a pool when it prices one of its grades, the rule the provider and the constraints already apply. A pool of ungraded places is priced by no grade, so only a tariff naming none is offered it.

The payload used to be NULL without a background or without place geometry. It now returns as long as there is a pool, so a standing hall has a picker: the blocks are the picker, and the map stands down.

The picker

One block per area, with a stepper. Each step posts to the same place-op endpoint a seat click uses, with op: pool, the area and a signed number of units. A take is a group hold, so SectionPoolBookable runs under the slot lock: the block cannot sell what the constraint would refuse, and a refusal comes back carrying what is really left, so the block corrects itself rather than lying.

Giving a unit back releases one line. A line holding several units (the calendar path can produce one) is reduced instead, so handing one unit back never hands back three. Reducing a hold only frees capacity, so it needs no capacity check of its own.

Pooled units join the seats in one selection panel, with the same remove affordance and price total, and Remove all clears both.

Verified

  • New kernel tests: PoolPickerTest (a standing hall has a payload with no place and no background; a pooled slice is a block beside the placed seats; sold units come off the block; an ungraded pool is offered only to a tariff naming no grade; nothing to show is still NULL) and PoolOpTest (taking units, a refusal that reports the truth and holds nothing, giving one back, reducing a multi-unit line, and a request naming no area being a bad request).
  • Whole yoyaku_placement kernel directory green: 21 classes. Placement Functional green: 4 tests.
  • phpcs (with warnings) and cspell clean; phpstan at 23 against a 25 baseline; eslint and stylelint show no new problem against the branch point (the JS keeps this file's existing idiom).
  • Exercised on a real venue: pooling the parterre of a 1422-seat hall yields 947 drawable places plus one block of 300 with its two sellable tariffs, and reverting the setting restores the hall.

Docs and translation

docs/placement.md loses its "not yet" caveat about the picker and gains what the block does; translations/yoyaku.fr.po gains the twelve new strings, including the plural for the units left.

Merge request reports

Loading