A booking that names no tariff is accepted on a resource that has them, uncapped and priced from the resource

Closes the hole by letting a resource declare that it sells tariffs, rather than having the engine infer it from whether a tariff row happens to exist.

The declaration

tariffs_enabled on yoyaku_resource, named after the existing guarantee_enabled, with a sellsTariffs() accessor. Per resource only, not resolved through the resource type and a site default: a lot of machinery for one bit, and a resource type covering both kinds is normal.

The guard

BookingManager::hold() refuses a line naming no tariff when its resource sells them. In the engine, beside the existing check, so every surface is covered at once rather than hardening one form and hoping the next caller remembers.

The other half of "uncapped", found while building this

limitFor() fell back to the slot's capacity when a named tariff had no slot tariff. That reads the room as one tariff's allocation, which would let each tariff promise the whole house, and when the slot carried no capacity (the normal state for a tariffed resource, since the generator gives the overall capacity only to resources defining no tariffs) it left a tariffed line unbounded too.

The same fallback existed in the availability read, so page and hold agreed with each other while both were wrong. Both now fall back to the tariff's own quota. The room still bounds the total separately, against the slot-wide consumption, so the effective limit is the lower of the two.

Vocabulary that caused it

  • default_quota becomes quota on yoyaku_resource_tariff. price is called price at all three scopes without ever being default_price: the entity name carries the scope, the field carries the meaning. And the field is now the allocation a slot tariff overrides, not merely a seed, so "default" was doubly wrong.
  • assertCapacity() becomes assertFits(), since it asserts a quota and a capacity. assertLimit()'s $capacity parameter becomes $limit, and boundedAvailability()'s $cap/$slot_cap become $quota/$capacity. All five helpers are private and referenced nowhere else, so this is free.

Refusing to orphan tariffs

A NoOrphanedTariffs constraint refuses turning the mode off while tariffs exist, naming how many are in the way. Deleting them silently would throw away an operator's pricing; leaving them silently would keep rows nothing reads. Only the transition is guarded, so a resource that never sold tariffs is unaffected.

Interface

The Tariffs tab and its add form are gated on the declaration, via _custom_access alongside the existing _entity_access, and the shortcut operation is gated too so it cannot link to a 403. A resource selling tariffs but holding none reads "it sells nothing. Add one." rather than showing an empty table, because that is a legitimate mid-setup state. The resource's own price field is hidden by #states when the mode is on, mirroring how guarantee_enabled reveals its dependents.

Testing

TariffsEnabledTest, seven cases. The two refusal cases were run against unfixed code and seen to fail before the guard existed; the four "must still work" cases passed throughout, so the class discriminates rather than ratifying. It covers the refusal, the refusal holding before any slot tariff exists, a tariffed line still working, an untariffed resource still booking without one, and the three constraint states.

Locally: phpcs clean (Drupal,DrupalPractice, --warning-severity=1), phpstan at the CI ruleset reporting only pre-existing new.static, cspell introducing no new word, and an eight-class smoke set green. The full suite is left to the pipeline, since a field rename reaches most of it.

Migration

No update hook: pre-1.0, reinstall-only. An existing site derives the flag once, on where a resource already has at least one tariff, and renames the default_quota column in place. Note the limitFor() change binds where nothing did before: a tariff with no slot tariff is now held to its own quota.

Not in scope

The quota-versus-capacity vocabulary is fixed only inside BookingManager's private helpers. "rate" as a lingering synonym for tariff, and whether a venue configuration should hide the capacity fields, are separate.

Merge request reports

Loading
Loading