Issue #3611662: Venue sections (first-class), seat geometry and party-adjacency auto-assignment

First slice of the venues-as-sections model (#3611662): the seated part, on top of the merged seating foundation (#3611637). It delivers the seat map and automatic party-adjacency assignment; general admission, section mode, per-slot enable/disable and GA capacity pools are a follow-up on the same entity.

What this adds

  • yoyaku_section — a first-class, translatable venue section (orchestre, corbeille, balcon, paradis). The public name lives once and localizes, instead of a string copied onto every seat.
  • Seat geometryseat.section becomes a reference to yoyaku_section; seats gain row and position, which is what makes "adjacent" definable.
  • SeatAssignment::proposeAdjacent() — proposes the best free seats for a party: a run of consecutive positions in one row when possible, otherwise a best-effort scattered set flagged so the caller can warn. Seats are held as a group, so each is re-checked by SeatBookable under the slot lock and nothing is oversold.
  • Picker — a per-section seat map and a "seat a party together" flow.

Capacity

Seated capacity stays the seat inventory itself (each seat sold once per slot, enforced by SeatBookable); this slice adds no numeric capacity field. A GA pool SUM (and the precomputed reserved counter that would optimize it) come with the general-admission follow-up.

Deferred (follow-up on this issue)

  • General-admission sections and mixed venues, section mode, per-slot enable/disable, GA capacity pools.
  • A graphical, click-on-a-map seat map (the server-rendered per-section/row layout here is the first step).

Testing

Kernel: section CRUD + translatable label, adjacency (contiguous, skip-taken, scattered fallback, allowed-category, group-hold authoritative under the lock). Functional: pick + book a seat, auto-assign a party of adjacent seats. phpcs (Drupal, DrupalPractice), phpstan and cspell clean locally.

Merge request reports

Loading