Issue #3617045: Offer only the tariffs the front being served has, and keep what a basket already holds

The plan drew every tariff of the resource. A tariff a hall holds back from one front was therefore on that front's plan, with its label and its price, and the booker who took it was refused at the hold, where RequestBooker::assertOnOffer() applies the rule the display skipped.

The read

VenueMapBuilder::slotTariffs() now asks BookingChannelFilter, rather than spelling the rule again. It is the one read #3616821 extracted, so all three readers are narrowed by the one edit: the tariffs a seat may be booked at, the tariffs that may book an area offered by quantity, and the per-order allowances, which until now published a limit for a tariff nobody on that front could book.

What a basket holds is not an offer

Filtering alone would have been worse than the bug. A seat held at a withdrawn tariff would find no entry in its grade's list, and renderSummary() falls back to the first entry: the select would show an offer the booker never picked and the total would price the seat at it. That is the failure bookedAt was added to prevent.

Nor may the held tariff be joined into the offers: that half is shared, permanent and keyed per front, so a tariff kept there for one visitor would be handed to the next.

So it travels with the basket, in the half built per request and never kept. mine() gains offers, and that half gains a seam of its own, hook_yoyaku_placement_map_basket_alter(), filled by yoyaku_payment (the price) and yoyaku_booking_fee (the mention) — the same two implementations in the same order as on the settled half, so the modules stay symmetric. It is empty on every request that holds nothing withdrawn, which is nearly all of them.

In the browser, such an offer is put back on the seats and the areas that hold it and on no others, marked withdrawn: no seat may be moved onto it, its stepper goes no higher than what is already held, and the row says why.

The booking page had the same gap

It builds its fields from the filtered list and passes the held quantities only as prefill, so a line held when a tariff was withdrawn got no field at all: still in the basket, still on the cart, gone from the page it is managed on. Reaching it needs no crossing of fronts, only an operator restricting a tariff mid-basket. SlotBookingForm now joins those targets in — it is cached per session already, so there is nothing to share and nothing to leak.

Coverage

  • VenueMapChannelOffersTest — the seat offers, an area's offers and the allowances are each narrowed; a held tariff never enters the shared offers, and travels with the basket instead.
  • SlotBookingChannelTest — the page offers what the front has, and a line held at a withdrawn tariff stays on it, at what they hold, saying why, and not wearing the words for a full house.
  • FeeBasketOfferTest — the basket's own offer is priced and worded through the new seam.

All three were seen to fail against f8e4dfc8 before the fix, on exactly the assertions that matter, and pass after it. Run green alongside VenueMapPriceTest, VenueMapChannelTest, FeeMapMentionTest, SlotBookingFormTest and SlotBookingPlacementTest.

BookingManager::tariffsByResource() carried a comment claiming to be the read the venue map is built from, which it never was; corrected, along with docs/booking-channels.md and docs/placement.md.

Coverage of the browser half

WithdrawnOfferPickerTest drives a browser over three scenarios on one install: a seat held at a tariff its front then withdraws keeps that offer and its price; a seat taken afterwards is not offered it; and the front that still offers it offers it to everybody.

It earned its place rather than being a formality. The payload carried the offer correctly while every use of it in the panel was wrong: the half that folds it onto the map dropped it at all four points that half is built, so the feature did not work at all in a browser while every server-side test passed and CI was green. A seat held at 15 was drawn and totalled at another tariff's 30.

All four new classes run their cases as scenarios on one install, following [#3616973].

Edited by Frank Mably

Merge request reports

Loading
Loading