Read a row's seat order from the venue drawing, so a curved row stops claiming its two ends are adjacent

Closes [#3615672].

What this changes

position is what every adjacency question is answered from, and nothing derived it. Two new services derive it from the drawing instead:

  • RowPath recovers a row's order from its coordinates: walk from one of the row's two ends (the furthest-apart pair) to the nearest seat not yet walked. Direction is not a geometric question, so the direction the venue already numbers in is kept, decided over the whole row rather than by whichever seat holds position 1.
  • PlacePositions reports the rows that disagree with the drawing (proposed(), never writes) and renumbers them 1..N per area (section:subsection + row, matching how PlaceGeometry groups a row). Only places that actually move are saved.

How it is seen and run

  • drush yoyaku:venue-positions <venue> reports, --fix writes. Exits non-zero on drift so a deploy can gate on it.
  • A Numbering tab on the venue draws each row threaded in position order, with a marker on the seat holding position 1 so direction is visible too, and names every drifted row in a table.
  • Importing a venue package derives a drawn row's positions and logs how many rows it corrected.
  • The shipped Auditorium package is corrected: 265 places over 11 scrambled rows and 2 with holes.

Not changed

No seating strategy, no PlaceGeometry, no SeatingShareOut, no PlaceAvailability, no PlaceCandidate, no VenueMapBuilder, no venue-map.js/.css — git diff is empty for all of them. The strategies were reading the data correctly. The booker's map payload is unchanged too, since the drawing already carried position, and the overlay is an admin-only library attached from one controller.

Performance

Measured on a 1422-seat hall: the booking path reads 907 candidates and chooses 12 seats across 3 tariffs in ~4.5 ms with no new query. The numbering check is 1 query / ~62 ms and runs only on the admin tab and the drush command.

One inferred increase, which is the fix working: closing the holes means runsOf() now finds runs it could not before (a stretch of 34 instead of at most 9), so consolidate() materialises more windows.

Tests

RowPathTest (the shapes that broke: a row that turns, centre-out numbering, mirrored directions), PlacePositionsTest (both defects, idempotence, report-does-not-write, undrawn rows, pooled places), and two cases in VenueIoTest. The bundled-example assertion was seen to fail against the unfixed venue.json and pass against the fixed one.

phpcs, phpstan, prettier, eslint and stylelint clean on the changed files. Docs in docs/seating.md, 11 French strings added.

Known gap

The new tab has no automated coverage — it needs a functional test against a served docroot.

Follow-ups filed: [#3615673] (aisles inside an area still read as adjacent) and [#3615674] (the cross-tariff share-out is not compactness-aware).

Merge request reports

Loading
Loading